class Gitlab::QA::Report::ResultsInIssues

Uses the API to create or update GitLab issues with the results of tests from RSpec report files.

Constants

RESULTS_SECTION_TEMPLATE

Private Instance Methods

add_issue_to_testcase(testcase, issue) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 129
def add_issue_to_testcase(testcase, issue)
  results_section = testcase.description.include?(RESULTS_SECTION_TEMPLATE) ? '' : RESULTS_SECTION_TEMPLATE

  gitlab.edit_issue(iid: testcase.iid, options: { description: (testcase.description + results_section + "\n\n#{issue.web_url}") })
end
create_testcase(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 64
def create_testcase(test)
  title = title_from_test(test)
  puts "Creating test case '#{title}' ..."

  gitlab.create_issue(
    title: title,
    description: new_testcase_description(test),
    labels: new_issue_labels(test),
    issue_type: 'test_case'
  )
end
error_and_stack_trace(text) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 219
def error_and_stack_trace(text)
  text.strip[/Error:(.*)/m, 1].to_s
end
failure_summary() click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 203
def failure_summary
  summary = [":x: ~\"#{pipeline}::failed\""]
  summary << "~\"quarantine\"" if quarantine_job?
  summary << "in job `#{Runtime::Env.ci_job_name}` in #{Runtime::Env.ci_job_url}"
  summary.join(' ')
end
find_issue(test, issue_type) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 121
def find_issue(test, issue_type)
  issues = search_issues(test: test, issue_type: 'issue')

  warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?

  issues.first
end
find_issue_by_iid(testcase, test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 109
def find_issue_by_iid(testcase, test)
  iid = issue_iid_from_testcase(testcase)

  return unless iid

  issues = search_issues(test: test, issue_type: 'issue', iid: iid)

  warn(%(Issue iid "#{iid}" not valid)) if issues.empty?

  issues.first
end
find_or_create_issue(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 96
def find_or_create_issue(test)
  issue = find_issue(test, 'issue')

  if issue
    puts "Found existing issue: #{issue.web_url}"
  else
    issue = create_issue(test)
    puts "Created new issue: #{issue.web_url}"
  end

  issue
end
find_testcase(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 49
def find_testcase(test)
  iid = iid_from_testcase_url(test.testcase)

  testcases = search_issues(test: test, issue_type: 'test_case', iid: iid)

  if iid && testcases.blank?
    warn(%(Test case url "#{test.testcase}" not valid))
    testcases = search_issues(test: test, issue_type: 'test_case')
  end

  warn(%(Too many test cases found with the file path "#{test.file}" and name "#{test.name}")) if testcases&.many?

  testcases.first
end
iid_from_testcase_url(url) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 76
def iid_from_testcase_url(url)
  return warn(%(\nPlease update #{url} to test case url")) if url&.include?('/-/issues/')

  url && url.split('/').last.to_i
end
issue_iid_from_testcase(testcase) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 86
def issue_iid_from_testcase(testcase)
  results = testcase.description.partition(RESULTS_SECTION_TEMPLATE).last if testcase.description.include?(RESULTS_SECTION_TEMPLATE)

  return puts "No issue link found" unless results

  issue_iid = results.split('/').last

  issue_iid&.to_i
end
new_issue_labels(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 149
def new_issue_labels(test)
  ['Quality', "devops::#{test.stage}", 'status::automated']
end
new_note_matches_discussion?(note, discussion) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 210
def new_note_matches_discussion?(note, discussion)
  note_error = error_and_stack_trace(note)
  discussion_error = error_and_stack_trace(discussion.notes.first['body'])

  return false if note_error.empty? || discussion_error.empty?

  note_error == discussion_error
end
new_testcase_description(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 82
def new_testcase_description(test)
  "#{new_issue_description(test)}#{RESULTS_SECTION_TEMPLATE}"
end
note_content(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 185
        def note_content(test)
          errors = test.failures.each_with_object([]) do |failure, text|
            text << <<~TEXT
              Error:
              ```
              #{failure['message']}
              ```

              Stacktrace:
              ```
              #{failure['stacktrace']}
              ```
            TEXT
          end.join("\n\n")

          "#{failure_summary}\n\n#{errors}"
        end
note_status(issue, test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 170
def note_status(issue, test)
  return false if test.skipped
  return false if test.failures.empty?

  note = note_content(test)

  gitlab.find_issue_discussions(iid: issue.iid).each do |discussion|
    return gitlab.add_note_to_issue_discussion_as_thread(iid: issue.iid, discussion_id: discussion.id, body: failure_summary) if new_note_matches_discussion?(note, discussion)
  end

  gitlab.create_issue_note(iid: issue.iid, note: note)

  true
end
report_test(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 29
def report_test(test)
  puts "Reporting test: #{test.file} | #{test.name}"

  testcase = find_testcase(test) || create_testcase(test)
  test.testcase ||= testcase.web_url.sub('/issues/', '/quality/test_cases/')

  issue = find_issue_by_iid(testcase, test)

  unless issue
    puts "No valid issue link found"
    issue = find_or_create_issue(test)

    add_issue_to_testcase(testcase, issue)
    puts "Added issue #{issue.web_url} to testcase #{testcase.web_url}"
  end

  update_labels(testcase, test)
  update_issue(issue, test)
end
run!() click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 15
def run!
  puts "Reporting test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."

  test_results_per_file do |test_results|
    puts "Reporting tests in #{test_results.path}"

    test_results.each do |test|
      report_test(test) unless test.skipped
    end

    test_results.write
  end
end
search_issues(test:, issue_type:, iid: nil) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 160
def search_issues(test:, issue_type:, iid: nil)
  gitlab.find_issues(iid: iid, options: { search: search_term(test) }) do |issue|
    issue.state == 'opened' && issue.issue_type == issue_type && issue.title.strip == title_from_test(test)
  end
end
search_term(test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 166
def search_term(test)
  %("#{partial_file_path(test.file)}" "#{search_safe(test.name)}")
end
up_to_date_labels(test:, issue: nil, new_labels: Set.new) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 153
def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
  labels = super
  labels |= new_issue_labels(test).to_set
  labels.delete_if { |label| label.start_with?("#{pipeline}::") }
  labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
end
update_issue(issue, test) click to toggle source
# File lib/gitlab/qa/report/results_in_issues.rb, line 135
def update_issue(issue, test)
  new_labels = issue_labels(issue)
  new_labels |= ['Testcase Linked']

  labels_updated = update_labels(issue, test, new_labels)
  note_posted = note_status(issue, test)

  if labels_updated || note_posted
    puts "Issue updated."
  else
    puts "Test passed, no update needed."
  end
end