This file is indexed.

/usr/lib/ruby/vendor_ruby/rspec/matchers/built_in/compound.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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
module RSpec
  module Matchers
    module BuiltIn
      # @api private
      # Base class for `and` and `or` compound matchers.
      # rubocop:disable ClassLength
      class Compound < BaseMatcher
        # @private
        attr_reader :matcher_1, :matcher_2, :evaluator

        def initialize(matcher_1, matcher_2)
          @matcher_1 = matcher_1
          @matcher_2 = matcher_2
        end

        # @private
        def does_not_match?(_actual)
          raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \
            "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \
            "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \
            "use `expect(...).to matcher.#{conjunction} matcher`."
        end

        # @api private
        # @return [String]
        def description
          "#{matcher_1.description} #{conjunction} #{matcher_2.description}"
        end

        def supports_block_expectations?
          matcher_supports_block_expectations?(matcher_1) &&
          matcher_supports_block_expectations?(matcher_2)
        end

        def expects_call_stack_jump?
          NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
          NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
        end

        # @api private
        # @return [Boolean]
        def diffable?
          matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2)
        end

        # @api private
        # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
        def expected
          return nil unless evaluator
          ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
        end

      protected

        def diffable_matcher_list
          list = []
          list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches?
          list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches?
          list
        end

      private

        def initialize_copy(other)
          @matcher_1 = @matcher_1.clone
          @matcher_2 = @matcher_2.clone
          super
        end

        def match(_expected, actual)
          evaluator_klass = if supports_block_expectations? && Proc === actual
                              NestedEvaluator
                            else
                              SequentialEvaluator
                            end

          @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
        end

        def indent_multiline_message(message)
          message.lines.map do |line|
            line =~ /\S/ ? '   ' + line : line
          end.join
        end

        def compound_failure_message
          "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \
          "\n\n...#{conjunction}:" \
          "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}"
        end

        def matcher_1_matches?
          evaluator.matcher_matches?(matcher_1)
        end

        def matcher_2_matches?
          evaluator.matcher_matches?(matcher_2)
        end

        def matcher_supports_block_expectations?(matcher)
          matcher.supports_block_expectations?
        rescue NoMethodError
          false
        end

        def matcher_is_diffable?(matcher)
          matcher.diffable?
        rescue NoMethodError
          false
        end

        def diffable_matcher_list_for(matcher)
          return [] unless matcher_is_diffable?(matcher)
          return matcher.diffable_matcher_list if Compound === matcher
          [matcher]
        end

        # For value expectations, we can evaluate the matchers sequentially.
        class SequentialEvaluator
          def initialize(actual, *)
            @actual = actual
          end

          def matcher_matches?(matcher)
            matcher.matches?(@actual)
          end
        end

        # Normally, we evaluate the matching sequentially. For an expression like
        # `expect(x).to foo.and bar`, this becomes:
        #
        #   expect(x).to foo
        #   expect(x).to bar
        #
        # For block expectations, we need to nest them instead, so that
        # `expect { x }.to foo.and bar` becomes:
        #
        #   expect {
        #     expect { x }.to foo
        #   }.to bar
        #
        # This is necessary so that the `expect` block is only executed once.
        class NestedEvaluator
          def initialize(actual, matcher_1, matcher_2)
            @actual        = actual
            @matcher_1     = matcher_1
            @matcher_2     = matcher_2
            @match_results = {}

            inner, outer = order_block_matchers

            @match_results[outer] = outer.matches?(Proc.new do |*args|
              @match_results[inner] = inner.matches?(inner_matcher_block(args))
            end)
          end

          def matcher_matches?(matcher)
            @match_results.fetch(matcher)
          end

        private

          # Some block matchers (such as `yield_xyz`) pass args to the `expect` block.
          # When such a matcher is used as the outer matcher, we need to forward the
          # the args on to the `expect` block.
          def inner_matcher_block(outer_args)
            return @actual if outer_args.empty?

            Proc.new do |*inner_args|
              unless inner_args.empty?
                raise ArgumentError, "(#{@matcher_1.description}) and " \
                  "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
                  "since both matchers pass arguments to the block."
              end

              @actual.call(*outer_args)
            end
          end

          # For a matcher like `raise_error` or `throw_symbol`, where the block will jump
          # up the call stack, we need to order things so that it is the inner matcher.
          # For example, we need it to be this:
          #
          #   expect {
          #     expect {
          #       x += 1
          #       raise "boom"
          #     }.to raise_error("boom")
          #   }.to change { x }.by(1)
          #
          # ...rather than:
          #
          #   expect {
          #     expect {
          #       x += 1
          #       raise "boom"
          #     }.to change { x }.by(1)
          #   }.to raise_error("boom")
          #
          # In the latter case, the after-block logic in the `change` matcher would never
          # get executed because the `raise "boom"` line would jump to the `rescue` in the
          # `raise_error` logic, so only the former case will work properly.
          #
          # This method figures out which matcher should be the inner matcher and which
          # should be the outer matcher.
          def order_block_matchers
            return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2)
            return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1)

            raise ArgumentError, "(#{@matcher_1.description}) and " \
              "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
              "because they both expect a call stack jump."
          end

          def self.matcher_expects_call_stack_jump?(matcher)
            matcher.expects_call_stack_jump?
          rescue NoMethodError
            false
          end
        end

        # @api public
        # Matcher used to represent a compound `and` expectation.
        class And < self
          # @api private
          # @return [String]
          def failure_message
            if matcher_1_matches?
              matcher_2.failure_message
            elsif matcher_2_matches?
              matcher_1.failure_message
            else
              compound_failure_message
            end
          end

        private

          def match(*)
            super
            matcher_1_matches? && matcher_2_matches?
          end

          def conjunction
            "and"
          end
        end

        # @api public
        # Matcher used to represent a compound `or` expectation.
        class Or < self
          # @api private
          # @return [String]
          def failure_message
            compound_failure_message
          end

        private

          def match(*)
            super
            matcher_1_matches? || matcher_2_matches?
          end

          def conjunction
            "or"
          end
        end
      end
    end
  end
end