diff --git a/bin/annotate_test_failures b/bin/annotate_test_failures
index 26234baf..b00a6ac5 100755
--- a/bin/annotate_test_failures
+++ b/bin/annotate_test_failures
@@ -10,6 +10,7 @@ require 'shellwords'
require 'net/http'
require 'json'
require 'optparse'
+require_relative '../lib/test_failure'
###################
# Parse arguments
@@ -43,67 +44,6 @@ end
# Helper methods
###################
-# Here's how report.junit XML files look like for failure vs success:
-#
-#
-# <unknown>:0
-#
-#
-# Traceback (most recent call last): ...
-#
-#
-#
-class TestFailure
- attr_reader :classname, :name, :message, :details
- attr_accessor :count
-
- def initialize(node)
- @classname = node['classname']
- @name = node['name']
- failure_node = node.elements['failure'] || node.elements['error']
- @message = failure_node['message']
- @details = failure_node.text
- @count = 1
- end
-
- 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
- #{@name} in #{@classname}#{times}
- #{@message}
- #{formatted_details}
-
- ENTRY
- end
-
- def key
- "#{@classname}.#{@name}"
- end
-
- def ultimately_succeeds?(parent_node:)
- all_same_test_nodes = parent_node.get_elements("testcase[@classname='#{@classname}' and @name='#{@name}']")
- # If last node found for that test doesn't have a or child, then test ultimately succeeded.
- !(all_same_test_nodes.last.elements['failure'] || all_same_test_nodes.last.elements['error'])
- end
-
- def ==(other)
- @classname == other.classname && @name == other.name && @message == other.message && @details == other.details
- end
-end
-
# Add a test_failure into a given array, or increment its count if it already exists
#
def record(test_failure, into:)
diff --git a/lib/test_failure.rb b/lib/test_failure.rb
new file mode 100644
index 00000000..f6f06c97
--- /dev/null
+++ b/lib/test_failure.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+# Here's how report.junit XML files look like for failure vs success:
+#
+#
+# <unknown>:0
+#
+#
+# Traceback (most recent call last): ...
+#
+#
+#
+class TestFailure
+ attr_reader :classname, :name, :message, :details
+ attr_accessor :count
+
+ def initialize(node)
+ @classname = node['classname']
+ @name = node['name']
+ failure_node = node.elements['failure'] || node.elements['error']
+ @message = failure_node['message']
+ @details = failure_node.text
+ @count = 1
+ end
+
+ 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
+ #{@name} in #{@classname}#{times}
+ #{@message}
+ #{formatted_details}
+
+ ENTRY
+ end
+
+ def key
+ "#{@classname}.#{@name}"
+ end
+
+ def ultimately_succeeds?(parent_node:)
+ all_same_test_nodes = parent_node.get_elements("testcase[@classname='#{@classname}' and @name='#{@name}']")
+ # If last node found for that test doesn't have a or child, then test ultimately succeeded.
+ !(all_same_test_nodes.last.elements['failure'] || all_same_test_nodes.last.elements['error'])
+ end
+
+ def ==(other)
+ @classname == other.classname && @name == other.name && @message == other.message && @details == other.details
+ end
+end