summaryrefslogtreecommitdiff
path: root/lib/gitlab/chat/output.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/chat/output.rb')
-rw-r--r--lib/gitlab/chat/output.rb93
1 files changed, 93 insertions, 0 deletions
diff --git a/lib/gitlab/chat/output.rb b/lib/gitlab/chat/output.rb
new file mode 100644
index 00000000000..411b1555a7d
--- /dev/null
+++ b/lib/gitlab/chat/output.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Class for gathering and formatting the output of a `Ci::Build`.
+ class Output
+ attr_reader :build
+
+ MissingBuildSectionError = Class.new(StandardError)
+
+ # The primary trace section to look for.
+ PRIMARY_SECTION = 'chat_reply'
+
+ # The backup trace section in case the primary one could not be found.
+ FALLBACK_SECTION = 'build_script'
+
+ # build - The `Ci::Build` to obtain the output from.
+ def initialize(build)
+ @build = build
+ end
+
+ # Returns a `String` containing the output of the build.
+ #
+ # The output _does not_ include the command that was executed.
+ def to_s
+ offset, length = read_offset_and_length
+
+ trace.read do |stream|
+ stream.seek(offset)
+
+ output = stream
+ .stream
+ .read(length)
+ .force_encoding(Encoding.default_external)
+
+ without_executed_command_line(output)
+ end
+ end
+
+ # Returns the offset to seek to and the number of bytes to read relative
+ # to the offset.
+ def read_offset_and_length
+ section = find_build_trace_section(PRIMARY_SECTION) ||
+ find_build_trace_section(FALLBACK_SECTION)
+
+ unless section
+ raise(
+ MissingBuildSectionError,
+ "The build_script trace section could not be found for build #{build.id}"
+ )
+ end
+
+ length = section[:byte_end] - section[:byte_start]
+
+ [section[:byte_start], length]
+ end
+
+ # Removes the line containing the executed command from the build output.
+ #
+ # output - A `String` containing the output of a trace section.
+ def without_executed_command_line(output)
+ # If `output.split("\n")` produces an empty Array then the slicing that
+ # follows it will produce a nil. For example:
+ #
+ # "\n".split("\n") # => []
+ # "\n".split("\n")[1..-1] # => nil
+ #
+ # To work around this we only "join" if we're given an Array.
+ if (converted = output.split("\n")[1..-1])
+ converted.join("\n")
+ else
+ ''
+ end
+ end
+
+ # Returns the trace section for the given name, or `nil` if the section
+ # could not be found.
+ #
+ # name - The name of the trace section to find.
+ def find_build_trace_section(name)
+ trace_sections.find { |s| s[:name] == name }
+ end
+
+ def trace_sections
+ @trace_sections ||= trace.extract_sections
+ end
+
+ def trace
+ @trace ||= build.trace
+ end
+ end
+ end
+end