# frozen_string_literal: true module GitlabQuality module TestTooling module Report class MergeRequestSlowTestsReport SLOW_TEST_MESSAGE = '' SLOW_TEST_LABEL = '/label ~"rspec:slow test detected"' SLOW_TEST_NOTE_SOURCE_CODE = 'Generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling/-/blob/main/lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb).' def initialize(token:, input_files:, merge_request_iid:, project: nil, dry_run: false, **_kwargs) @project = project @gitlab_merge_request = (dry_run ? GitlabClient::MergeRequestDryClient : GitlabClient::MergeRequest).new(token: token, project: project, merge_request_iid: merge_request_iid) @files = Array(input_files) @merge_request_iid = merge_request_iid @slow_tests = [] end def invoke! validate_input! run! end private attr_reader :gitlab_merge_request, :files, :project, :merge_request_iid, :slow_tests def run! puts "Reporting slow tests in MR #{merge_request_iid}" TestResults::Builder.new(files).test_results_per_file do |test_results| puts "=> Reporting #{test_results.count} tests in #{test_results.path}" @slow_tests += slow_related_tests(find_slow_tests(test_results)) end @slow_tests.uniq! @slow_tests.reject!(&:failed?) return unless @slow_tests.any? upsert_mr_note(@slow_tests) end def related_test?(slow_test) base_file = slow_test.file.split('/').last.gsub('_spec.rb', '') merge_request_changed_files.any? { |change| change =~ /#{base_file}/ } end def slow_related_tests(slow_test_results) slow_test_results.select do |slow_test| related_test?(slow_test) end end def merge_request_changed_files @merge_request_changed_files ||= gitlab_merge_request.merge_request_changed_files end def find_slow_tests(test_results) test_results.select(&:slow_test?) end def note_header [ SLOW_TEST_MESSAGE, SLOW_TEST_LABEL, ":tools: #{SLOW_TEST_NOTE_SOURCE_CODE}\n", "---\n", ":snail: Slow tests detected in this merge request. These slow tests might be related to this merge request's changes.", "
Click to expand\n", '| Job | File | Name | Duration | Expected duration |', '| --- | --- | --- | --- | --- |' ] end def slow_test_rows(slow_test) rows = [] slow_test.each do |test| rows << slow_test_table_row(test) end rows end def build_note(slow_test) rows = note_header + slow_test_rows(slow_test) + ["\n
"] rows.join("\n") end def note_comment_includes_slow_test?(gitlab_note, slow_test) gitlab_note.include?("[##{slow_test.ci_job_id}](#{slow_test.ci_job_url}) | #{slow_test.test_file_link}") end def slow_test_table_row(slow_test) row = [ "[##{slow_test.ci_job_id}](#{slow_test.ci_job_url})", slow_test.test_file_link, slow_test.name, "#{slow_test.run_time} s", "< #{slow_test.max_duration_for_test} s" ] "| #{row.join(' | ')} |" end def add_slow_test_rows(gitlab_note, slow_tests) gitlab_note.gsub!("\n\n", "") slow_tests.each do |slow_test| gitlab_note += "\n#{slow_test_table_row(slow_test)}" unless note_comment_includes_slow_test?(gitlab_note, slow_test) end "#{gitlab_note}\n\n" end def upsert_mr_note(slow_tests) existing_note = gitlab_merge_request.find_note(body: SLOW_TEST_MESSAGE) if existing_note puts "Update note for merge request: #{merge_request_iid}" up_to_date_note = add_slow_test_rows(existing_note.body, slow_tests) gitlab_merge_request.update_note(id: existing_note['id'], note: up_to_date_note) if existing_note.body != up_to_date_note else up_to_date_note = build_note(slow_tests) puts "Create note for merge request: #{merge_request_iid}" gitlab_merge_request.create_note(note: up_to_date_note) end end def validate_input! assert_project! assert_input_files!(files) end def assert_project! return if project abort "Please provide a valid project ID or path with the `-p/--project` option!" end def assert_input_files!(files) return if Dir.glob(files).any? abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`" end end end end end