diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-25 15:12:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-25 15:12:11 +0000 |
commit | 82c63f420c7fc2229a44aa336d656a9712884f83 (patch) | |
tree | 7ae316f671e194adcbc7712e853e5397bf161a36 /scripts/pipeline_test_report_builder.rb | |
parent | e5d3d8c323872cb5dee33c5388b3157294c30343 (diff) | |
download | gitlab-ce-82c63f420c7fc2229a44aa336d656a9712884f83.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'scripts/pipeline_test_report_builder.rb')
-rwxr-xr-x | scripts/pipeline_test_report_builder.rb | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/scripts/pipeline_test_report_builder.rb b/scripts/pipeline_test_report_builder.rb new file mode 100755 index 00000000000..56491d40a3e --- /dev/null +++ b/scripts/pipeline_test_report_builder.rb @@ -0,0 +1,153 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'optparse' +require 'time' +require 'fileutils' +require 'uri' +require 'cgi' +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 + def initialize(options) + @project = options.delete(:project) + @mr_id = options.delete(:mr_id) || Host::DEFAULT_OPTIONS[:mr_id] + @instance_base_url = options.delete(:instance_base_url) || Host::DEFAULT_OPTIONS[:instance_base_url] + @output_file_path = options.delete(:output_file_path) + end + + def test_report_for_latest_pipeline + build_test_report_json_for_pipeline(previous_pipeline) + end + + def execute + if output_file_path + FileUtils.mkdir_p(File.dirname(output_file_path)) + end + + File.open(output_file_path, 'w') do |file| + file.write(test_report_for_latest_pipeline) + end + end + + private + + attr_reader :project, :mr_id, :instance_base_url, :output_file_path + + def project_api_base_url + "#{instance_base_url}/api/v4/projects/#{CGI.escape(project)}" + end + + def project_base_url + "#{instance_base_url}/#{project}" + end + + def previous_pipeline + # Top of the list will always be the current pipeline + # Second from top will be the previous pipeline + pipelines_for_mr.sort_by { |a| -Time.parse(a['created_at']).to_i }[1] + end + + def pipelines_for_mr + fetch("#{project_api_base_url}/merge_requests/#{mr_id}/pipelines") + end + + def failed_builds_for_pipeline(pipeline_id) + fetch("#{project_api_base_url}/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 + def test_report_for_build(pipeline_id, build_id) + fetch("#{project_base_url}/-/pipelines/#{pipeline_id}/tests/suite.json?build_ids[]=#{build_id}") + end + + def build_test_report_json_for_pipeline(pipeline) + # empty file if no previous failed pipeline + return {}.to_json if pipeline.nil? || pipeline['status'] != 'failed' + + test_report = {} + + puts "Discovered last failed pipeline (#{pipeline['id']}) for MR!#{mr_id}" + + failed_builds_for_test_stage = failed_builds_for_pipeline(pipeline['id']).select do |failed_build| + failed_build['stage'] == 'test' + end + + puts "#{failed_builds_for_test_stage.length} failed builds in test stage found..." + + if failed_builds_for_test_stage.any? + test_report['suites'] ||= [] + + failed_builds_for_test_stage.each do |failed_build| + test_report['suites'] << test_report_for_build(pipeline['id'], failed_build['id']) + end + end + + test_report.to_json + end + + def fetch(uri_str) + uri = URI(uri_str) + + puts "URL: #{uri}" + + 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 + raise "Unexpected response: #{response.value}" + end + end + end + + JSON.parse(body) + end +end + +if $0 == __FILE__ + options = Host::DEFAULT_OPTIONS.dup + + OptionParser.new do |opts| + opts.on("-p", "--project PROJECT", String, "Project where to find the merge request(defaults to $CI_PROJECT_ID)") do |value| + options[:project] = value + end + + opts.on("-m", "--mr-id MR_ID", String, "A merge request ID") do |value| + options[:mr_id] = value + end + + opts.on("-i", "--instance-base-url INSTANCE_BASE_URL", String, "URL of the instance where project and merge request resides") do |value| + options[:instance_base_url] = value + end + + opts.on("-o", "--output-file-path OUTPUT_PATH", String, "A path for output file") do |value| + options[:output_file_path] = value + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end + end.parse! + + PipelineTestReportBuilder.new(options).execute +end |