Skip to content

Commit

Permalink
Omit block for test failure empty details in annotation (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
mokagio authored Jun 8, 2023
1 parent 7dbc9ad commit c252bcf
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 56 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ _None._

### Bug Fixes

_None._
- `annotate_test_failures` no longer prints an empty `<code>` box when reporting a `failure` with no extra `details`. [#63]

### Internal Changes

Expand Down
126 changes: 71 additions & 55 deletions bin/annotate_test_failures
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# Usage:
# annotate_test_failures [options] [junit-report-file-path]
Expand All @@ -16,14 +18,17 @@ slack_channel = nil
slack_webhook = ENV['SLACK_WEBHOOK'] # default value inferred from env var if not provided explicitly
args = OptionParser.new do |opts|
opts.banner = <<~HELP
Usage: annotate_test_failures [junit-report-file-path] [--slack CHANNEL] [--slack-webhook URL]
Usage: annotate_test_failures [junit-report-file-path] [--slack CHANNEL] [--slack-webhook URL]
Annotates the Buildkite build with a summary of failed and flaky tests based on a JUnit report file (defaults to using `build/results/report.junit`).
Optionally also posts the same info to a Slack channel.
HELP
opts.on('--slack CHANNEL_NAME', 'The name of the Slack channel to post the failure summary to') { |v| slack_channel = '#' + v.delete_prefix('#') }
opts.on('--slack CHANNEL_NAME', 'The name of the Slack channel to post the failure summary to') { |v| slack_channel = "##{v.delete_prefix('#')}" }
opts.on('--slack-webhook URL', 'The Slack Webhook URL to use if --slack is used. Defaults to the value of the `SLACK_WEBHOOK` env var') { |v| slack_webhook = v }
opts.on_tail("-h", "--help", "Show this help message") { puts opts; exit }
opts.on_tail('-h', '--help', 'Show this help message') do
puts opts
exit
end
end.parse!
junit_path = args.first || File.join('build', 'results', 'report.junit')
title = ENV.fetch('BUILDKITE_LABEL', 'Tests')
Expand Down Expand Up @@ -63,13 +68,23 @@ class TestFailure

def to_s
times = @count > 1 ? " (#{@count} times)" : ''

# Do not render a code block for the failure details if there are none
formatted_details = if @details.strip.empty?
''
else
<<~DETAILS
```
#{@details}
```
DETAILS
end

<<~ENTRY
<details><summary><tt>#{@name}</tt> in <tt>#{@classname}</tt>#{times}</summary>
#{@message}
```
#{@details}
```
<pre>#{@message}</pre>
#{formatted_details}
</details>
ENTRY
end
Expand All @@ -80,7 +95,8 @@ class TestFailure

def ultimately_succeeds?(parent_node:)
all_same_test_nodes = parent_node.get_elements("testcase[@classname='#{@classname}' and @name='#{@name}']")
!(all_same_test_nodes.last.elements['failure'] || all_same_test_nodes.last.elements['error']) # If last node found for that test doesn't have a <failure> or <error> child, then test ultimately succeeded.
# If last node found for that test doesn't have a <failure> or <error> child, then test ultimately succeeded.
!(all_same_test_nodes.last.elements['failure'] || all_same_test_nodes.last.elements['error'])
end

def ==(other)
Expand Down Expand Up @@ -118,58 +134,58 @@ end
# Given a list of failures, parse list and send a slack notification with the test names in the payload
#
def send_slack_notification(slack_webhook, slack_channel, title, list)
failing_tests = list.map { |item| "`#{item.name}` in `#{item.classname}`" }
assertion_failures_count = list.count
test_text = (assertion_failures_count == 1) ? "Test" : "Tests"

slack_message_payload = {
"channel": "#{slack_channel}",
"username": "#{ENV['BUILDKITE_PIPELINE_NAME']} Tests Failures",
"icon_emoji": ":fire:",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *#{assertion_failures_count} #{test_text} Failed in #{ENV['BUILDKITE_PIPELINE_NAME']} - #{title}*"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Build",
"emoji": true
},
"value": "build",
"url": "#{ENV['BUILDKITE_BUILD_URL']}##{ENV['BUILDKITE_JOB_ID']}",
"action_id": "button-action"
}
},
{
"type": "divider"
failing_tests = list.map { |item| "`#{item.name}` in `#{item.classname}`" }
assertion_failures_count = list.count
test_text = assertion_failures_count == 1 ? 'Test' : 'Tests'

slack_message_payload = {
"channel": slack_channel,
"username": "#{ENV['BUILDKITE_PIPELINE_NAME']} Tests Failures",
"icon_emoji": ':fire:',
"blocks": [
{
"type": 'section',
"text": {
"type": 'mrkdwn',
"text": ":warning: *#{assertion_failures_count} #{test_text} Failed in #{ENV['BUILDKITE_PIPELINE_NAME']} - #{title}*"
},
{
"type": "section",
"accessory": {
"type": 'button',
"text": {
"type": "mrkdwn",
"text": "*Failing #{test_text}:*\n#{failing_tests.join("\n")}"
}
"type": 'plain_text',
"text": 'Build',
"emoji": true
},
"value": 'build',
"url": "#{ENV['BUILDKITE_BUILD_URL']}##{ENV['BUILDKITE_JOB_ID']}",
"action_id": 'button-action'
}
]
}
},
{
"type": 'divider'
},
{
"type": 'section',
"text": {
"type": 'mrkdwn',
"text": "*Failing #{test_text}:*\n#{failing_tests.join("\n")}"
}
}
]
}

json_payload = JSON.generate(slack_message_payload)
json_payload = JSON.generate(slack_message_payload)

# Send message to Slack
uri = URI(slack_webhook)
response = Net::HTTP.post(uri, json_payload, "Content-Type" => "application/json")
# Send message to Slack
uri = URI(slack_webhook)
response = Net::HTTP.post(uri, json_payload, 'Content-Type' => 'application/json')

# Check response status
if response.code == "200"
puts "✅ Notification Sent!"
else
puts "❌ Failed to send notification. Response code: #{response.code}"
end
# Check response status
if response.code == '200'
puts '✅ Notification Sent!'
else
puts "❌ Failed to send notification. Response code: #{response.code}"
end
end

###################
Expand Down

0 comments on commit c252bcf

Please sign in to comment.