# frozen_string_literal: true require 'active_support/core_ext/object/blank' module GitlabQuality module TestTooling module TestResults class TestResult def self.from_json(report) JsonTestResult.new(report) end def self.from_junit(report) JUnitTestResult.new(report) end attr_accessor :report, :failures def initialize(report) self.report = report self.failures = failures_from_exceptions end def stage @stage ||= file[%r{(?:api|browser_ui)/(?:(?:\d+_)?(\w+))}, 1] end def name raise NotImplementedError end def file raise NotImplementedError end def skipped raise NotImplementedError end private def failures_from_exceptions raise NotImplementedError end class JsonTestResult < TestResult def name report['full_description'] end def file report['file_path'].delete_prefix('./') end def status report['status'] end def ci_job_url report['ci_job_url'] end def skipped status == 'pending' end def testcase report['testcase'] end def testcase=(new_testcase) report['testcase'] = new_testcase end def failure_issue report['failure_issue'] end def failure_issue=(new_failure_issue) report['failure_issue'] = new_failure_issue end def quarantine? # The value for 'quarantine' could be nil, a hash, a string, # or true (if the test just has the :quarantine tag) # But any non-nil or false value should means the test is in quarantine report['quarantine'].present? end def quarantine_type report['quarantine']['type'] if quarantine? end def quarantine_issue report['quarantine']['issue'] if quarantine? end def screenshot? report['screenshot'].present? end def failure_screenshot report['screenshot']['image'] if screenshot? end def product_group? report['product_group'].present? end def product_group report['product_group'] if product_group? end private # rubocop:disable Metrics/AbcSize def failures_from_exceptions return [] unless report.key?('exceptions') report['exceptions'].map do |exception| spec_file_first_index = exception['backtrace'].rindex do |line| line.include?(File.basename(report['file_path'])) end exception['message'].gsub!(/(private_token=)[\w-]+/, '********') Array(exception['message_lines']).each { |line| line.gsub!(/(private_token=)([\w-]+)/, '********') } { 'message' => "#{exception['class']}: #{exception['message']}", 'message_lines' => exception['message_lines'], 'stacktrace' => "#{format_message_lines(exception['message_lines'])}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}", 'correlation_id' => exception['correlation_id'] } end end def format_message_lines(message_lines) message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines end # rubocop:enable Metrics/AbcSize end class JUnitTestResult < TestResult def name report['name'] end def file report['file'].delete_prefix('./') end def skipped report.search('skipped').any? end attr_accessor :testcase # Ignore it for now private # rubocop:disable Metrics/AbcSize def failures_from_exceptions failures = report.search('failure') return [] if failures.empty? failures.map do |exception| trace = exception.content.split("\n").map(&:strip) spec_file_first_index = trace.rindex do |line| line.include?(File.basename(report['file'])) end exception['message'].gsub!(/(private_token=)[\w-]+/, '********') { 'message' => "#{exception['type']}: #{exception['message']}", 'stacktrace' => trace.slice(0..spec_file_first_index).join("\n") } end end # rubocop:enable Metrics/AbcSize end end end end end