Skip to content

Commit 1646c88

Browse files
authored
Improve Semaphore test report grouping and summaries (#3291)
* `Fix Semaphore test report grouping for MCP` | #3290 * Add Markdown test summary to Semaphore reports | #3290 * Align Semaphore reports with passed instrumentation jobs | #3290 * Fix Semaphore report generation and ADB diagnostics timeout | #3290 * Report final instrumentation test outcomes in Semaphore | #3290 * Show instrumentation stacktraces in Semaphore reports | #3290
1 parent fcd8c47 commit 1646c88

7 files changed

Lines changed: 472 additions & 35 deletions

‎.semaphore/semaphore.yml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,9 @@ after_pipeline:
162162
jobs:
163163
- name: Publish Results
164164
commands:
165-
- test-results gen-pipeline-report
165+
- checkout
166+
- sudo apt-get install -y jq
167+
- test-results gen-pipeline-report --generate-mcp-summary
168+
- artifact pull workflow "test-results/${SEMAPHORE_PIPELINE_ID}.json" -d pipeline-test-results.json
169+
- ./script/ci-generate-test-report.sh pipeline-test-results.json test-report.md
170+
- artifact push workflow -f -d .semaphore/REPORT.md test-report.md

‎script/ci-after-fail-debug.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77

88
set -euo pipefail
99

10+
ADB_BIN="$(command -v adb)"
11+
ADB_COMMAND_TIMEOUT="${ADB_COMMAND_TIMEOUT:-15s}"
1012
SUMMARY_FAILURES=0
1113

14+
adb() {
15+
timeout "$ADB_COMMAND_TIMEOUT" "$ADB_BIN" "$@"
16+
}
17+
1218
print_section() {
1319
local title="$1"
1420

‎script/ci-generate-test-report.sh‎

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
5+
# Contributors: denbond7
6+
#
7+
8+
set -euo pipefail
9+
10+
if [[ "$#" -ne 2 ]]; then
11+
echo "Usage: $0 <pipeline-test-results.json> <report.md>"
12+
exit 1
13+
fi
14+
15+
input_file="$1"
16+
output_file="$2"
17+
18+
if [[ ! -f "$input_file" ]]; then
19+
echo "Pipeline test results file does not exist: $input_file"
20+
exit 1
21+
fi
22+
23+
if ! command -v jq >/dev/null 2>&1; then
24+
echo "jq is required to generate the test report"
25+
exit 1
26+
fi
27+
28+
work_dir="$(mktemp -d)"
29+
json_file="$input_file"
30+
31+
cleanup() {
32+
rm -rf "$work_dir"
33+
}
34+
35+
trap cleanup EXIT
36+
37+
# test-results stores JSON reports compressed with gzip while retaining the .json extension.
38+
if gzip -t "$input_file" 2>/dev/null; then
39+
json_file="$work_dir/pipeline-test-results.json"
40+
gzip -cd "$input_file" > "$json_file"
41+
fi
42+
43+
branch="${SEMAPHORE_GIT_BRANCH:-local}"
44+
commit_sha="${SEMAPHORE_GIT_SHA:-$(git rev-parse HEAD 2>/dev/null || printf 'unknown')}"
45+
short_commit_sha="${commit_sha:0:7}"
46+
47+
jq -r \
48+
--arg branch "$branch" \
49+
--arg commit "$short_commit_sha" \
50+
'
51+
def markdown:
52+
tostring
53+
| gsub("\\|"; "&#124;")
54+
| gsub("`"; "&#96;")
55+
| gsub("\\r"; "")
56+
| gsub("\\n"; "<br>");
57+
58+
def duration:
59+
(. // 0) as $nanoseconds
60+
| if $nanoseconds >= 60000000000 then
61+
(((($nanoseconds / 6000000000) | floor) / 10) | tostring) + "m"
62+
else
63+
(((($nanoseconds / 1000000) | floor) / 1000) | tostring) + "s"
64+
end;
65+
66+
[.testResults[]?] as $groups
67+
| [
68+
$groups[] as $group
69+
| $group.suites[]? as $suite
70+
| $suite.tests[]?
71+
| {
72+
group: $group.name,
73+
suite: $suite.name,
74+
name: .name,
75+
state: .state,
76+
duration: .duration,
77+
message: (.failure.message // .error.message // "")
78+
}
79+
] as $tests
80+
| [$tests[] | select(.state == "failed" or .state == "error")] as $failed_tests
81+
| ($tests | sort_by(.duration // 0) | reverse | .[0:10]) as $slowest_tests
82+
| ([$groups[].summary.total] | add // 0) as $total
83+
| ([$groups[].summary.passed] | add // 0) as $passed
84+
| ([$groups[].summary.failed] | add // 0) as $failed
85+
| ([$groups[].summary.error] | add // 0) as $errors
86+
| ([$groups[].summary.skipped] | add // 0) as $skipped
87+
| ([$groups[].summary.disabled] | add // 0) as $disabled
88+
| ([$groups[].summary.duration] | add // 0) as $total_duration
89+
| (
90+
[
91+
"# Test report",
92+
"",
93+
(if $total == 0
94+
then "**Status:** ⚠️ No test results were published"
95+
elif ($failed + $errors) > 0
96+
then "**Status:** ❌ " + (($failed + $errors) | tostring) + " test(s) failed"
97+
else "**Status:** ✅ All tests passed"
98+
end),
99+
"",
100+
"_Branch `" + ($branch | markdown) + "`, commit `" + ($commit | markdown) + "`._",
101+
"",
102+
"## Summary",
103+
"",
104+
"| Total | Passed | Failed | Errors | Skipped | Disabled | Test duration |",
105+
"| ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
106+
"| " + ($total | tostring)
107+
+ " | " + ($passed | tostring)
108+
+ " | " + ($failed | tostring)
109+
+ " | " + ($errors | tostring)
110+
+ " | " + ($skipped | tostring)
111+
+ " | " + ($disabled | tostring)
112+
+ " | " + ($total_duration | duration) + " |",
113+
"",
114+
"## Test groups",
115+
"",
116+
"| Group | Tests | Passed | Failed | Errors | Skipped | Suites | Duration |",
117+
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |"
118+
]
119+
+ ($groups | map(
120+
"| " + (.name | markdown)
121+
+ " | " + (.summary.total | tostring)
122+
+ " | " + (.summary.passed | tostring)
123+
+ " | " + (.summary.failed | tostring)
124+
+ " | " + (.summary.error | tostring)
125+
+ " | " + (.summary.skipped | tostring)
126+
+ " | " + ((.suites | length) | tostring)
127+
+ " | " + (.summary.duration | duration) + " |"
128+
))
129+
+ [
130+
"",
131+
"## Failed tests",
132+
""
133+
]
134+
+ (if ($failed_tests | length) == 0 then
135+
["✅ No failed tests."]
136+
else
137+
[
138+
"| Group | Suite | Test | Result | Duration | Message |",
139+
"| --- | --- | --- | --- | ---: | --- |"
140+
]
141+
+ ($failed_tests | map(
142+
"| " + (.group | markdown)
143+
+ " | " + (.suite | markdown)
144+
+ " | `" + (.name | markdown) + "`"
145+
+ " | " + (.state | markdown)
146+
+ " | " + (.duration | duration)
147+
+ " | " + (.message | markdown) + " |"
148+
))
149+
end)
150+
+ [
151+
"",
152+
"## Slowest tests",
153+
""
154+
]
155+
+ (if ($slowest_tests | length) == 0 then
156+
["No test results were published."]
157+
else
158+
[
159+
"| Suite | Test | Result | Duration |",
160+
"| --- | --- | --- | ---: |"
161+
]
162+
+ ($slowest_tests | map(
163+
"| " + (.suite | markdown)
164+
+ " | `" + (.name | markdown) + "`"
165+
+ " | " + (.state | markdown)
166+
+ " | " + (.duration | duration) + " |"
167+
))
168+
end)
169+
)
170+
| join("\n")
171+
' "$json_file" > "$output_file"
172+
173+
echo "Generated test report: $output_file"

‎script/ci-get-and-publish-debug-info-as-artifact.sh‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88
set -euo pipefail
99

10+
ADB_BIN="$(command -v adb)"
11+
ADB_COMMAND_TIMEOUT="${ADB_COMMAND_TIMEOUT:-15s}"
12+
13+
adb() {
14+
timeout "$ADB_COMMAND_TIMEOUT" "$ADB_BIN" "$@"
15+
}
16+
1017
if [[ "$SEMAPHORE_JOB_NAME" =~ ^Lint.* ]]; then
1118
# Do nothing for 'Lint(structural quality)' job.
1219
exit 0
@@ -23,8 +30,11 @@ fi
2330
if [[ "$SEMAPHORE_JOB_NAME" =~ ^Instrumentation.* ]]; then
2431
# store full logcat log
2532
echo "Collect logcat logs as logcat.txt.gz for $SEMAPHORE_JOB_NAME"
26-
adb logcat -d | gzip > "$HOME/logcat.txt.gz"
27-
artifact push job "$HOME/logcat.txt.gz"
33+
if adb logcat -d | gzip > "$HOME/logcat.txt.gz"; then
34+
artifact push job "$HOME/logcat.txt.gz"
35+
else
36+
echo "Could not collect logcat within $ADB_COMMAND_TIMEOUT, skipping"
37+
fi
2838

2939
echo "Store the device's screenshot for $SEMAPHORE_JOB_NAME"
3040
if adb shell screencap -p /sdcard/screencap.png; then

0 commit comments

Comments
 (0)