lib/gitlab/qa/report/test_result.rb



# frozen_string_literal: true

require 'active_support/core_ext/object/blank'

module Gitlab
  module QA
    module Report
      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']
          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

          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

              {
                'message' => "#{exception['class']}: #{exception['message']}",
                'message_lines' => exception['message_lines'],
                'stacktrace' => "#{exception['message_lines'].join("\n")}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}"
              }
            end
          end
          # rubocop:enable Metrics/AbcSize
        end

        class JUnitTestResult < TestResult
          def name
            report['name']
          end

          def file
            report['file']
          end

          def skipped
            report.search('skipped').any?
          end

          attr_accessor :testcase # Ignore it for now

          private

          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

              {
                'message' => "#{exception['type']}: #{exception['message']}",
                'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
              }
            end
          end
        end
      end
    end
  end
end