Skip to content

Commit 30a961b

Browse files
committed
new feature: report specified amount of slowest tests (0 by default)
1 parent 2bd625d commit 30a961b

File tree

9 files changed

+141
-76
lines changed

9 files changed

+141
-76
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ Default: `junit`
5656

5757
The buildkite annotation context to use. Useful to differentiate multiple runs of this plugin in a single pipeline.
5858

59+
### `report-slowest` (optional)
60+
Default: `0`
61+
62+
Adds a table with the specified amount of slowest tests listed in the JUnit XML files. The annotation will also be shown when all tests are successful.
63+
5964
## Developing
6065

6166
To test the plugin hooks (in Bash) and the junit parser (in Ruby):

hooks/command

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ echo "--- :junit: Download the junits"
1010
artifacts_dir="$(pwd)/$(mktemp -d "junit-annotate-plugin-artifacts-tmp.XXXXXXXXXX")"
1111
annotation_dir="$(pwd)/$(mktemp -d "junit-annotate-plugin-annotation-tmp.XXXXXXXXXX")"
1212
annotation_path="${annotation_dir}/annotation.md"
13+
annotation_style="info"
14+
fail_build=0
1315

1416
function cleanup {
1517
rm -rf "${artifacts_dir}"
@@ -30,6 +32,7 @@ buildkite-agent artifact download \
3032

3133
echo "--- :junit: Processing the junits"
3234

35+
set +e
3336
docker \
3437
--log-level "error" \
3538
run \
@@ -38,25 +41,35 @@ docker \
3841
--volume "$PLUGIN_DIR/ruby:/src" \
3942
--env "BUILDKITE_PLUGIN_JUNIT_ANNOTATE_JOB_UUID_FILE_PATTERN=${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_JOB_UUID_FILE_PATTERN:-}" \
4043
--env "BUILDKITE_PLUGIN_JUNIT_ANNOTATE_FAILURE_FORMAT=${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_FAILURE_FORMAT:-}" \
44+
--env "BUILDKITE_PLUGIN_JUNIT_ANNOTATE_REPORT_SLOWEST=${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_REPORT_SLOWEST:-}" \
4145
ruby:2.7-alpine ruby /src/bin/annotate /junits \
4246
> "$annotation_path"
4347

48+
if [[ $? -eq 64 ]]; then # special exit code to signal test failures
49+
annotation_style="error"
50+
if [[ "${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_FAIL_BUILD_ON_ERROR:-false}" =~ (true|on|1) ]]; then
51+
fail_build=1
52+
fi
53+
fi
54+
55+
set -e
56+
4457
cat "$annotation_path"
4558

4659
if grep -q "<details>" "$annotation_path"; then
47-
4860
if ! check_size; then
49-
echo "--- :warning: Failures too large to annotate"
50-
echo "The failures are too large to create a build annotation. Please inspect the failed JUnit artifacts manually."
61+
echo "--- :warning: Report too large to annotate"
62+
echo "The report are too large to create a build annotation. Please inspect the failed JUnit artifacts manually."
5163
else
5264
echo "--- :buildkite: Creating annotation"
5365
# shellcheck disable=SC2002
54-
cat "$annotation_path" | buildkite-agent annotate --context "${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_CONTEXT:-junit}" --style error
66+
cat "$annotation_path" | buildkite-agent annotate --context "${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_CONTEXT:-junit}" --style "$annotation_style"
5567
fi
68+
fi
5669

57-
if [[ "${BUILDKITE_PLUGIN_JUNIT_ANNOTATE_FAIL_BUILD_ON_ERROR:-false}" =~ (true|on|1) ]]
58-
then
59-
echo "--- :boom: Failing build due to error"
60-
exit 1
61-
fi
70+
if ((fail_build)); then
71+
echo "--- :boom: Failing build due to error"
72+
exit 1
73+
else
74+
exit 0
6275
fi

plugin.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ configuration:
1818
type: boolean
1919
context:
2020
type: string
21+
report-slowest:
22+
type: integer
2123
required:
2224
- artifacts
2325
additionalProperties: false

ruby/bin/annotate

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@ job_pattern = '-(.*).xml' if !job_pattern || job_pattern.empty?
1717
failure_format = ENV['BUILDKITE_PLUGIN_JUNIT_ANNOTATE_FAILURE_FORMAT']
1818
failure_format = 'classname' if !failure_format || failure_format.empty?
1919

20-
class Failure < Struct.new(:name, :failed_test, :body, :job, :type, :message)
20+
report_slowest = ENV['BUILDKITE_PLUGIN_JUNIT_ANNOTATE_REPORT_SLOWEST'].to_i
21+
22+
class Failure < Struct.new(:name, :unit_name, :body, :job, :type, :message)
23+
end
24+
25+
class Timing < Struct.new(:name, :unit_name, :time)
2126
end
2227

2328
junit_report_files = Dir.glob(File.join(junits_dir, "**", "*"))
2429
testcases = 0
2530
failures = []
31+
timings = []
2632

2733
def text_content(element)
2834
# Handle mulptiple CDATA/text children elements
@@ -55,48 +61,65 @@ junit_report_files.sort.each do |file|
5561
REXML::XPath.each(doc, '//testsuite/testcase') do |testcase|
5662
testcases += 1
5763
name = testcase.attributes['name'].to_s
58-
failed_test = testcase.attributes[failure_format].to_s
64+
unit_name = testcase.attributes[failure_format].to_s
65+
time = testcase.attributes['time'].to_f
66+
timings << Timing.new(name, unit_name, time)
5967
testcase.elements.each("failure") do |failure|
60-
failures << Failure.new(name, failed_test, text_content(failure), job, :failure, message_content(failure))
68+
failures << Failure.new(name, unit_name, text_content(failure), job, :failure, message_content(failure))
6169
end
6270
testcase.elements.each("error") do |error|
63-
failures << Failure.new(name, failed_test, text_content(error), job, :error, message_content(error))
71+
failures << Failure.new(name, unit_name, text_content(error), job, :error, message_content(error))
6472
end
6573
end
6674
end
6775

68-
STDERR.puts "--- ❓ Checking failures"
76+
STDERR.puts "--- ✍️ Preparing annotation"
6977
STDERR.puts "#{testcases} testcases found"
7078

71-
if failures.empty?
72-
STDERR.puts "There were no failures/errors 🙌"
73-
exit 0
74-
else
79+
if failures.any?
7580
STDERR.puts "There #{failures.length == 1 ? "is 1 failure/error" : "are #{failures.length} failures/errors" } 😭"
76-
end
7781

78-
STDERR.puts "--- ✍️ Preparing annotation"
82+
failures_count = failures.select {|f| f.type == :failure }.length
83+
errors_count = failures.select {|f| f.type == :error }.length
84+
puts [
85+
failures_count == 0 ? nil : (failures_count == 1 ? "1 failure" : "#{failures_count} failures"),
86+
errors_count === 0 ? nil : (errors_count == 1 ? "1 error" : "#{errors_count} errors"),
87+
].compact.join(" and ") + ":\n\n"
88+
89+
failures.each do |failure|
90+
puts "<details>"
91+
puts "<summary><code>#{failure.name} in #{failure.unit_name}</code></summary>\n\n"
92+
if failure.message
93+
puts "<p>#{failure.message.chomp.strip}</p>\n\n"
94+
end
95+
if failure.body
96+
puts "<pre><code>#{CGI.escapeHTML(failure.body.chomp.strip)}</code></pre>\n\n"
97+
end
98+
if failure.job
99+
puts "in <a href=\"##{failure.job}\">Job ##{failure.job}</a>"
100+
end
101+
puts "</details>"
102+
puts "" unless failure == failures.last
103+
end
79104

80-
failures_count = failures.select {|f| f.type == :failure }.length
81-
errors_count = failures.select {|f| f.type == :error }.length
105+
else
106+
STDERR.puts "There were no failures/errors 🙌"
107+
end
82108

83-
puts [
84-
failures_count == 0 ? nil : (failures_count == 1 ? "1 failure" : "#{failures_count} failures"),
85-
errors_count === 0 ? nil : (errors_count == 1 ? "1 error" : "#{errors_count} errors"),
86-
].compact.join(" and ") + ":\n\n"
109+
if report_slowest > 0
110+
STDERR.puts "Reporting slowest tests ⏱"
87111

88-
failures.each do |failure|
89112
puts "<details>"
90-
puts "<summary><code>#{failure.name} in #{failure.failed_test}</code></summary>\n\n"
91-
if failure.message
92-
puts "<p>#{failure.message.chomp.strip}</p>\n\n"
93-
end
94-
if failure.body
95-
puts "<pre><code>#{CGI.escapeHTML(failure.body.chomp.strip)}</code></pre>\n\n"
96-
end
97-
if failure.job
98-
puts "in <a href=\"##{failure.job}\">Job ##{failure.job}</a>"
113+
puts "<summary>#{report_slowest} slowest tests</summary>\n\n"
114+
puts "<table>"
115+
puts "<thead><tr><th>Unit</th><th>Test</th><th>Time</th></tr></thead>"
116+
puts "<tbody>"
117+
timings.sort_by(&:time).reverse.take(report_slowest).each do |timing|
118+
puts "<tr><td>#{timing.unit_name}</td><td>#{timing.name}</td><td>#{timing.time}</td></tr>"
99119
end
120+
puts "</tbody>"
121+
puts "</table>"
100122
puts "</details>"
101-
puts "" unless failure == failures.last
102123
end
124+
125+
exit 64 if failures.any? # special exit code to signal test failures

0 commit comments

Comments
 (0)