# frozen_string_literal: true module GitlabQuality module TestTooling module Report # Uses the API to create GitLab issues for any passed test coming from JSON test reports. # We expect the test reports to come from rspec-retry, or from a new RSpec process where # we retried failing specs. # # - Takes the JSON test reports like rspec-*.json (typically from rspec-retry gem)` # - Takes a project where flaky test issues should be created # - For every passed test in the report: # - Find issue by test hash # - Reopen issue if it already exists, but is closed class FlakyTestIssue < ReportAsIssue include Concerns::IssueReports NEW_ISSUE_LABELS = Set.new(['type::maintenance', 'priority::3', 'severity::3', 'failure::flaky-test']).freeze FOUND_IN_MR_LABEL = 'found:in MR' FOUND_IN_MASTER_LABEL = 'found:master' def initialize(token:, input_files:, project: nil, merge_request_iid: nil, confidential: false, dry_run: false, **_kwargs) super(token: token, input_files: input_files, project: project, confidential: confidential, dry_run: dry_run) @merge_request_iid = merge_request_iid end private attr_reader :merge_request_iid def run! puts "Reporting flaky 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| next if test.status != 'passed' # We only want failed tests that passed in the end create_flaky_issue(test) end end end def new_issue_title(test) "Flaky test in #{super}" end def new_issue_labels(_test) found_label = if !merge_request_iid || merge_request_iid.empty? FOUND_IN_MASTER_LABEL else FOUND_IN_MR_LABEL end NEW_ISSUE_LABELS + [found_label] end def new_issue_description(test) super + [ "Flaky tests were detected, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) " \ "to learn more about how to reproduce them.", initial_reports_section(test) ].compact.join("\n\n") end def create_flaky_issue(test) puts " => Finding existing issues for flaky test '#{test.name}' (run time: #{test.run_time} seconds)..." issues = find_issues_by_hash(test_hash(test)) issues.each do |issue| puts " => Existing issue link #{issue.web_url}." puts " => Adding the flaky test to the existing issue..." add_report_to_issue(issue, test) if issue.state == 'closed' puts " => Issue is closed. Reopening it." reopen_issue(issue) end end create_issue(test) unless issues.any? end def add_report_to_issue(issue, test) gitlab.edit_issue(iid: issue.iid, options: { description: add_report_to_issue_description(issue, test) }) end def reopen_issue(issue) gitlab.edit_issue(iid: issue.iid, options: { state_event: 'reopen' }) end end end end end