# frozen_string_literal: true module GitlabQuality module TestTooling module TestResult class JsonTestResult < BaseTestResult FILE_BASE_URL = "https://gitlab.com/gitlab-org/gitlab/-/blob/master/" PRIVATE_TOKEN_REGEX = /(private_token=)[\w-]+/ OTHER_TESTS_MAX_DURATION = 45.40 # seconds TestLevelSpecification = Struct.new(:regex, :max_duration) TEST_LEVEL_SPECIFICATIONS = [ TestLevelSpecification.new(%r{spec/features/}, 50.13), TestLevelSpecification.new(%r{spec/(controllers|requests)/}, 19.20), TestLevelSpecification.new(%r{spec/lib/}, 27.12), TestLevelSpecification.new(%r{qa/specs/features/}, 240) ].freeze def name report.fetch('full_description') end def file report.fetch('file_path').delete_prefix('./') end def status report.fetch('status') end def skipped? status == 'pending' end def ci_job_url report.fetch('ci_job_url', '') end def testcase report.fetch('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 !!quarantine end def quarantine_type quarantine['type'] if quarantine? end def quarantine_issue quarantine['issue'] if quarantine? end def screenshot? !!screenshot end def screenshot_image screenshot['image'] if screenshot? end def product_group report['product_group'].to_s end def product_group? product_group != '' end def feature_category report['feature_category'] end def run_time report['run_time'].to_f.round(2) end def example_id report['id'] end def line_number report['line_number'] end def level report['level'] end def ci_job_id report['ci_job_url'].split('/').last end def failures # rubocop:disable Metrics/AbcSize @failures ||= report.fetch('exceptions', []).filter_map do |exception| backtrace = exception['backtrace'] next unless backtrace.respond_to?(:rindex) spec_file_first_index = backtrace.rindex do |line| line.include?(File.basename(report['file_path'])) end message = redact_private_token(exception['message']) message_lines = Array(exception['message_lines']).map { |line| redact_private_token(line) } { 'message' => "#{exception['class']}: #{message}", 'message_lines' => message_lines, 'stacktrace' => "#{format_message_lines(message_lines)}\n#{backtrace.slice(0..spec_file_first_index).join("\n")}", 'correlation_id' => exception['correlation_id'] } end end def failures? failures.any? end def allowed_to_be_slow? !!report['allowed_to_be_slow'] end def slow_test? !allowed_to_be_slow? && run_time > max_duration_for_test end def max_duration_for_test test_level_specification = TEST_LEVEL_SPECIFICATIONS.find do |test_level_specification| example_id =~ test_level_specification.regex end return OTHER_TESTS_MAX_DURATION unless test_level_specification test_level_specification.max_duration end def test_file_link path_prefix = file.start_with?('qa/') ? 'qa/' : '' "[`#{path_prefix}#{file}#L#{line_number}`](#{FILE_BASE_URL}#{path_prefix}#{file}#L#{line_number})" end private def quarantine report.fetch('quarantine', nil) end def screenshot report.fetch('screenshot', nil) end def format_message_lines(message_lines) message_lines.is_a?(Array) ? message_lines.join("\n") : message_lines end def redact_private_token(text) text.gsub(PRIVATE_TOKEN_REGEX, '********') end end end end end