# frozen_string_literal: true module GitlabQuality module TestTooling module TestMeta module Processor class AddToQuarantineProcessor < MetaProcessor QUARANTINE_METADATA = <<~META , %{indentation}quarantine: { %{indentation} issue: '%{issue_url}', %{indentation} type: %{quarantine_type} %{indentation}}%{suffix} META class << self # Execute the processor # # @param [Hash<String,String>] spec the spec to update # @option spec [String] :file_path the path to the spec file # @option spec [String] :stage the stage of the test # @option spec [String] :failure_issue the issue url of the failure # @option spec [String] :name the name of the example # @param [TestMetaUpdater] context instance of TestMetaUpdater def execute(spec, context) # rubocop:disable Metrics/AbcSize @context = context @file_path = spec["file_path"] devops_stage = spec["stage"] @failure_issue_url = spec["failure_issue"] @example_name = spec["name"] @issue_id = failure_issue_url.split('/').last # split url segment, last segment of path is the issue id @mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name) @failure_issue = context.fetch_issue(iid: issue_id) return unless proceed_with_merge_request? @file_contents = context.get_file_contents(file_path) new_content, changed_line_no = add_quarantine_metadata branch = context.create_branch("#{issue_id}-quarantine-#{SecureRandom.hex(4)}", example_name, context.ref) context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content) Quarantine end-to-end test Quarantine #{example_name} COMMIT_MESSAGE context.create_merge_request(mr_title, branch) do <<~MARKDOWN ## What does this MR do? Quarantines the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1}) This test was identified in the reliable e2e test report: #{context.report_issue} ### E2E Test Failure issue(s) #{failure_issue_url} ### Check-list - [ ] General code guidelines check-list - [ ] [Code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) - [ ] [Style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html) - [ ] Quarantine test check-list - [ ] Follow the [Quarantining Tests guide](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests). - [ ] Confirm the test has a [`quarantine:` tag with the specified quarantine type](https://about.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantined-test-types). - [ ] Note if the test should be [quarantined for a specific environment](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/execution_context_selection.html#quarantine-a-test-for-a-specific-environment). - [ ] (Optionally) In case of an emergency (e.g. blocked deployments), consider adding labels to pick into auto-deploy (~"Pick into auto-deploy" ~"priority::1" ~"severity::1"). - [ ] To ensure a faster turnaround, ask in the `#quality_maintainers` Slack channel for someone to review and merge the merge request, rather than assigning it directly. <!-- Base labels. --> /label ~"Quality" ~"QA" ~"type::maintenance" ~"maintenance::pipelines" <!-- Choose the stage that appears in the test path, e.g. ~"devops::create" for `qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`. --> /label ~"devops::#{devops_stage}" <div align="center"> (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc}) </div> MARKDOWN end end # Performs post processing. Takes a list of MRs and posts them in a note on report_issue and Slack # # @param [Gitlab::ObjectifiedHash] merge_requests def post_process(merge_requests) web_urls = merge_requests.compact.map { |mr| "- #{mr.web_url}\n" }.join return if web_urls.empty? context.post_note_on_report_issue(<<~ISSUE_NOTE) The following merge requests have been created to quarantine the unstable tests: #{web_urls} ISSUE_NOTE context.post_message_on_slack(<<~MSG) *Action Required!* The following merge requests have been created to quarantine the unstable tests identified in the reliable test report: #{context.report_issue} #{web_urls} Maintainers are requested to review and merge. Thank you. MSG end private attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name, :issue_id, :mr_title, :failure_issue # Checks if the failure issue is closed or if there is already an MR open # # @return [Boolean] def proceed_with_merge_request? if context.issue_is_closed?(failure_issue) puts " Failure issue '#{failure_issue_url}' is closed. Will not proceed with creating MR." return false end open_mrs = context.existing_merge_requests(title: mr_title) if open_mrs.any? puts " An open MR already exists for '#{example_name}': #{open_mrs.first['web_url']}. Will not proceed with creating MR." return false end true end # Add quarantine metadata to the file content and replace it # # @return [Array<String, Integer>] first value holds the new content, the second value holds the line number of the test def add_quarantine_metadata # rubocop:disable Metrics/AbcSize matched_lines = context.find_example_match_lines(file_contents, example_name) context.update_matched_line(matched_lines.last, file_contents.dup) do |line| indentation = context.indentation(line) if line.include?(',') && line.split.last != 'do' line[line.index(',')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ',', quarantine_type: quarantine_type) else line[line.rindex(' ')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ' ', quarantine_type: quarantine_type) end line end end # Returns the quarantine type based on the failure scoped label # # @return [String] def quarantine_type case context.issue_scoped_label(failure_issue, 'failure')&.split('::')&.last when 'new', 'investigating' ':investigating' when 'external-dependency' ':external_dependency' when 'broken-test' ':broken' when 'bug' ':bug' when 'flaky-test' ':flaky' when 'stale-test' ':stale' when 'test-environment' ':test_environment' else ':investigating' end end end end end end end end