This file is indexed.

/usr/lib/ruby/vendor_ruby/rspec/expectations/failure_aggregator.rb is in ruby-rspec-expectations 3.4.0c3e0m1s1-1ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
module RSpec
  module Expectations
    # @private
    class FailureAggregator
      attr_reader :block_label, :metadata

      def aggregate
        RSpec::Support.with_failure_notifier(self) do
          begin
            yield
          rescue ExpectationNotMetError => e
            # Normally, expectation failures will be notified via the `call` method, below,
            # but since the failure notifier uses a thread local variable, failing expectations
            # in another thread will still raise. We handle that here and categorize it as part
            # of `failures` rather than letting it fall through and be categorized as part of
            # `other_errors`.
            failures << e
          rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
            # While it is normally a bad practice to rescue `Exception`, it's important we do
            # so here. It's low risk (`notify_aggregated_failures` below will re-raise the exception,
            # or raise a `MultipleExpectationsNotMetError` that includes the exception), and it's
            # essential that the user is notified of expectation failures that may have already
            # occurred in the `aggregate_failures` block. Those expectation failures may provide
            # important diagnostics for understanding why this exception occurred, and if we simply
            # allowed this exception to be raised as-is, it would (wrongly) suggest to the user
            # that the expectation passed when it did not, which would be quite confusing.
            other_errors << e
          end
        end

        notify_aggregated_failures
      end

      def failures
        @failures ||= []
      end

      def other_errors
        @other_errors ||= []
      end

      # This method is defined to satisfy the callable interface
      # expected by `RSpec::Support.with_failure_notifier`.
      def call(failure, options)
        source_id = options[:source_id]
        return if source_id && @seen_source_ids.key?(source_id)

        @seen_source_ids[source_id] = true
        assign_backtrace(failure) unless failure.backtrace
        failures << failure
      end

    private

      if RSpec::Support::Ruby.jruby?
        # On JRuby, `caller` and `raise` produce different backtraces with regards to `.java`
        # stack frames. It's important that we use `raise` for JRuby to produce a backtrace
        # that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
        # so that rspec-core's truncation logic can work properly on it to list the backtrace
        # relative to the `aggregate_failures` block.
        def assign_backtrace(failure)
          raise failure
        rescue failure.class => e
          failure.set_backtrace(e.backtrace)
        end
      else
        # Using `caller` performs better (and is simpler) than `raise` on most Rubies.
        def assign_backtrace(failure)
          failure.set_backtrace(caller)
        end
      end

      def initialize(block_label, metadata)
        @block_label     = block_label
        @metadata        = metadata
        @seen_source_ids = {} # don't want to load stdlib set
      end

      def notify_aggregated_failures
        all_errors = failures + other_errors

        case all_errors.size
        when 0 then return nil
        when 1 then RSpec::Support.notify_failure all_errors.first
        else RSpec::Support.notify_failure MultipleExpectationsNotMetError.new(self)
        end
      end
    end

    # Exception raised from `aggregate_failures` when multiple expectations fail.
    class MultipleExpectationsNotMetError
      # @return [String] The fully formatted exception message.
      def message
        @message ||= (["#{summary}:"] + enumerated_failures + enumerated_errors).join("\n\n")
      end

      # @return [Array<RSpec::Expectations::ExpectationNotMetError>] The list of expectation failures.
      def failures
        @failure_aggregator.failures
      end

      # @return [Array<Exception>] The list of other exceptions.
      def other_errors
        @failure_aggregator.other_errors
      end

      # @return [Array<Exception>] The list of expectation failures and other exceptions, combined.
      attr_reader :all_exceptions

      # @return [String] The user-assigned label for the aggregation block.
      def aggregation_block_label
        @failure_aggregator.block_label
      end

      # @return [Hash] The metadata hash passed to `aggregate_failures`.
      def aggregation_metadata
        @failure_aggregator.metadata
      end

      # @return [String] A summary of the failure, including the block label and a count of failures.
      def summary
        "Got #{exception_count_description} from failure aggregation " \
        "block#{block_description}"
      end

      # return [String] A description of the failure/error counts.
      def exception_count_description
        failure_count = pluralize("failure", failures.size)
        return failure_count if other_errors.empty?
        error_count = pluralize("other error", other_errors.size)
        "#{failure_count} and #{error_count}"
      end

    private

      def initialize(failure_aggregator)
        @failure_aggregator = failure_aggregator
        @all_exceptions = failures + other_errors
      end

      def block_description
        return "" unless aggregation_block_label
        " #{aggregation_block_label.inspect}"
      end

      def pluralize(noun, count)
        "#{count} #{noun}#{'s' unless count == 1}"
      end

      def enumerated(exceptions, index_offset)
        exceptions.each_with_index.map do |exception, index|
          index += index_offset
          formatted_message = yield exception
          "#{index_label index}#{indented formatted_message, index}"
        end
      end

      def enumerated_failures
        enumerated(failures, 0, &:message)
      end

      def enumerated_errors
        enumerated(other_errors, failures.size) do |error|
          "#{error.class}: #{error.message}"
        end
      end

      def indented(failure_message, index)
        line_1, *rest = failure_message.strip.lines.to_a
        first_line_indentation = ' ' * (longest_index_label_width - width_of_label(index))

        first_line_indentation + line_1 + rest.map do |line|
          line =~ /\S/ ? indentation + line : line
        end.join
      end

      def indentation
        @indentation ||= ' ' * longest_index_label_width
      end

      def longest_index_label_width
        @longest_index_label_width ||= width_of_label(failures.size)
      end

      def width_of_label(index)
        index_label(index).chars.count
      end

      def index_label(index)
        "  #{index + 1}) "
      end
    end
  end
end