# frozen_string_literal: true module GitlabQuality module TestTooling module Report # Uses the API to create GitLab issues for slow tests # # - Takes the JSON test reports like rspec-*.json` # - Takes a project where slow issues should be created # - Find issue by title (with test description or test file) # - Add test metadata, duration to the issue with group and category labels class SlowTestIssue < ReportAsIssue include Concerns::FindSetDri include Concerns::GroupAndCategoryLabels NEW_ISSUE_LABELS = Set.new(['test', 'type::maintenance', 'maintenance::performance', 'priority::3', 'severity::3', 'rspec profiling', 'rspec:slow test']).freeze SEARCH_LABELS = %w[test maintenance::performance].freeze JOB_URL_REGEX = %r{(?https://(?[\w.]+)/(?[\w\-./]+)/-/jobs/\d+)} REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?.+)\)$/ MultipleIssuesFound = Class.new(StandardError) private def run! puts "Reporting slow tests in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`." TestResults::Builder.new(files).test_results_per_file do |test_results| puts "=> Reporting #{test_results.count} tests in #{test_results.path}" test_results.each do |test| create_slow_issue(test) if test.slow_test? end end write_issues_log_file end def new_issue_title(test) "Slow test in #{super}" end def new_issue_description(test) super + <<~DESCRIPTION.chomp Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed) to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests). Add `allowed_to_be_slow: true` to the RSpec test if this is a legit slow test and close the issue. #{reports_section(test)} DESCRIPTION end def reports_section(test) <<~REPORTS ### Reports (1) #{report_list_item(test)} REPORTS end def report_list_item(test) "1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')})" end def create_slow_issue(test) puts " => Finding existing issues for slow test '#{test.name}' (run time: #{test.run_time} seconds)..." issues = find_issues(test, SEARCH_LABELS, state: 'opened') if issues.blank? issues << create_issue(test) else issues.each do |issue| puts " => Existing issue link #{issue['web_url']}" update_reports(issue, test) end end collect_issues(test, issues) rescue MultipleIssuesFound => e warn(e.message) end def update_reports(issue, test) # We reopen closed issues to not lose any history state_event = issue.state == 'closed' ? 'reopen' : nil issue_attrs = { description: up_to_date_issue_description(issue.description, test) } issue_attrs[:state_event] = state_event if state_event gitlab.edit_issue(iid: issue.iid, options: issue_attrs) puts " => Added a report in '#{issue.title}': #{issue.web_url}!" end def up_to_date_issue_description(issue_description, test) new_issue_description = if issue_description.include?('### Reports') # We count the number of existing reports. reports_count = issue_description .scan(REPORT_ITEM_REGEX) .size.to_i + 1 issue_description.sub(/^### Reports.*$/, "### Reports (#{reports_count})") else # For issue with the legacy format, we add the Reports section reports_count = issue_description .scan(JOB_URL_REGEX) .size.to_i + 1 "#{issue_description}\n\n### Reports (#{reports_count})" end "#{new_issue_description}\n#{report_list_item(test)}" end end end end end