debian-mirror-gitlab/scripts/pipeline_test_report_builder.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

182 lines
5.7 KiB
Ruby
Raw Normal View History

2021-12-11 22:18:48 +05:30
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require 'time'
require 'fileutils'
require 'uri'
require 'net/http'
require 'json'
require_relative 'api/default_options'
# Request list of pipelines for MR
# https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab/merge_requests/69053/pipelines
# Find latest failed pipeline
# Retrieve list of failed builds for test stage in pipeline
# https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab/pipelines/363788864/jobs/?scope=failed
# Retrieve test reports for these builds
# https://gitlab.com/gitlab-org/gitlab/-/pipelines/363788864/tests/suite.json?build_ids[]=1555608749
# Push into expected format for failed tests
class PipelineTestReportBuilder
2023-04-23 21:23:45 +05:30
DEFAULT_OPTIONS = {
2023-05-27 22:25:52 +05:30
target_project: Host::DEFAULT_OPTIONS[:target_project] || API::DEFAULT_OPTIONS[:project],
current_pipeline_id: API::DEFAULT_OPTIONS[:pipeline_id],
2023-04-23 21:23:45 +05:30
mr_iid: Host::DEFAULT_OPTIONS[:mr_iid],
api_endpoint: API::DEFAULT_OPTIONS[:endpoint],
output_file_path: 'test_results/test_reports.json',
pipeline_index: :previous
}.freeze
2021-12-11 22:18:48 +05:30
def initialize(options)
@target_project = options.delete(:target_project)
2023-05-27 22:25:52 +05:30
@current_pipeline_id = options.delete(:current_pipeline_id)
2023-04-23 21:23:45 +05:30
@mr_iid = options.delete(:mr_iid)
@api_endpoint = options.delete(:api_endpoint).to_s
@output_file_path = options.delete(:output_file_path).to_s
@pipeline_index = options.delete(:pipeline_index).to_sym
2021-12-11 22:18:48 +05:30
end
def execute
2023-04-23 21:23:45 +05:30
FileUtils.mkdir_p(File.dirname(output_file_path))
2021-12-11 22:18:48 +05:30
File.open(output_file_path, 'w') do |file|
2023-04-23 21:23:45 +05:30
file.write(test_report_for_pipeline)
2021-12-11 22:18:48 +05:30
end
end
2023-04-23 21:23:45 +05:30
def test_report_for_pipeline
build_test_report_json_for_pipeline
end
def latest_pipeline
2023-05-27 22:25:52 +05:30
fetch("#{target_project_api_base_url}/pipelines/#{current_pipeline_id}")
2023-04-23 21:23:45 +05:30
end
2021-12-11 22:18:48 +05:30
def previous_pipeline
2023-04-23 21:23:45 +05:30
# Top of the list will always be the latest pipeline
2021-12-11 22:18:48 +05:30
# Second from top will be the previous pipeline
2023-04-23 21:23:45 +05:30
pipelines_sorted_descending[1]
2021-12-11 22:18:48 +05:30
end
private
2023-05-27 22:25:52 +05:30
attr_reader :target_project, :current_pipeline_id, :mr_iid, :api_endpoint, :output_file_path, :pipeline_index
2023-04-23 21:23:45 +05:30
def pipeline
@pipeline ||=
case pipeline_index
when :latest
latest_pipeline
when :previous
previous_pipeline
else
raise "[PipelineTestReportBuilder] Unsupported pipeline_index `#{pipeline_index}` (allowed index: `latest` and `previous`!"
end
end
def pipelines_sorted_descending
# Top of the list will always be the current pipeline
# Second from top will be the previous pipeline
pipelines_for_mr.sort_by { |a| -a['id'] }
end
2021-12-11 22:18:48 +05:30
def pipeline_project_api_base_url(pipeline)
2023-04-23 21:23:45 +05:30
"#{api_endpoint}/projects/#{pipeline['project_id']}"
2021-12-11 22:18:48 +05:30
end
def target_project_api_base_url
2023-04-23 21:23:45 +05:30
"#{api_endpoint}/projects/#{target_project}"
2021-12-11 22:18:48 +05:30
end
def pipelines_for_mr
2023-04-23 21:23:45 +05:30
@pipelines_for_mr ||= fetch("#{target_project_api_base_url}/merge_requests/#{mr_iid}/pipelines")
2021-12-11 22:18:48 +05:30
end
2023-04-23 21:23:45 +05:30
def failed_builds_for_pipeline
2021-12-11 22:18:48 +05:30
fetch("#{pipeline_project_api_base_url(pipeline)}/pipelines/#{pipeline['id']}/jobs?scope=failed&per_page=100")
end
# Method uses the test suite endpoint to gather test results for a particular build.
# Here we request individual builds, even though it is possible to supply multiple build IDs.
# The reason for this; it is possible to lose the job context and name when requesting multiple builds.
# Please see for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69053#note_709939709
2023-04-23 21:23:45 +05:30
def test_report_for_build(pipeline_url, build_id)
fetch("#{pipeline_url}/tests/suite.json?build_ids[]=#{build_id}").tap do |suite|
suite['job_url'] = job_url(pipeline_url, build_id)
end
2022-08-13 15:12:31 +05:30
rescue Net::HTTPServerException => e
raise e unless e.response.code.to_i == 404
2023-04-23 21:23:45 +05:30
puts "[PipelineTestReportBuilder] Artifacts not found. They may have expired. Skipping this build."
2021-12-11 22:18:48 +05:30
end
2023-04-23 21:23:45 +05:30
def build_test_report_json_for_pipeline
2021-12-11 22:18:48 +05:30
# empty file if no previous failed pipeline
2023-04-23 21:23:45 +05:30
return {}.to_json if pipeline.nil?
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
test_report = { 'suites' => [] }
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
puts "[PipelineTestReportBuilder] Discovered #{pipeline_index} failed pipeline (##{pipeline['id']}) for MR!#{mr_iid}"
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
failed_builds_for_pipeline.each do |failed_build|
next if failed_build['stage'] != 'test'
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
test_report['suites'] << test_report_for_build(pipeline['web_url'], failed_build['id'])
end
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
test_report['suites'].compact!
2021-12-11 22:18:48 +05:30
2023-04-23 21:23:45 +05:30
puts "[PipelineTestReportBuilder] #{test_report['suites'].size} failed builds in test stage found..."
2021-12-11 22:18:48 +05:30
test_report.to_json
end
2023-04-23 21:23:45 +05:30
def job_url(pipeline_url, build_id)
pipeline_url.sub(%r{/pipelines/.+}, "/jobs/#{build_id}")
end
2021-12-11 22:18:48 +05:30
def fetch(uri_str)
uri = URI(uri_str)
2023-04-23 21:23:45 +05:30
puts "[PipelineTestReportBuilder] URL: #{uri}"
2021-12-11 22:18:48 +05:30
request = Net::HTTP::Get.new(uri)
body = ''
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(request) do |response|
case response
when Net::HTTPSuccess
body = response.read_body
else
2023-04-23 21:23:45 +05:30
raise "[PipelineTestReportBuilder] Unexpected response: #{response.value}"
2021-12-11 22:18:48 +05:30
end
end
end
JSON.parse(body)
end
end
2022-11-25 23:54:43 +05:30
if $PROGRAM_NAME == __FILE__
2023-04-23 21:23:45 +05:30
options = PipelineTestReportBuilder::DEFAULT_OPTIONS.dup
2021-12-11 22:18:48 +05:30
OptionParser.new do |opts|
opts.on("-o", "--output-file-path OUTPUT_PATH", String, "A path for output file") do |value|
options[:output_file_path] = value
end
2023-04-23 21:23:45 +05:30
opts.on("-p", "--pipeline-index [latest|previous]", String, "What pipeline to retrieve (defaults to `#{PipelineTestReportBuilder::DEFAULT_OPTIONS[:pipeline_index]}`)") do |value|
options[:pipeline_index] = value
end
2021-12-11 22:18:48 +05:30
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
PipelineTestReportBuilder.new(options).execute
end