rspec / rspec-expectations

Provides a readable API to express expected outcomes of a code example
https://rspec.info
MIT License
1.26k stars 397 forks source link

Slow sequential And #1331

Open Lukom opened 2 years ago

Lukom commented 2 years ago

Subject of the issue

It appears that if And compounds are sequential then tests are running for a long time. Eg.:

expect('qwe').
  to include('asd').
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd')).
    and(include('asd'))

this is running on my computer for 15 seconds, and each additional condition doubles the previous duration.

It's not a problem with nested and:

expect('qwe').
  to include('asd').
    and include('asd').
      and include('asd').
        and include('asd').
          and include('asd').
            and include('asd').
              and include('asd').
                and include('asd').
                  and include('asd').
                    and include('asd').
                      and include('asd').
                        and include('asd').
                          and include('asd').
                            and include('asd')

This is instant. But I prefer the previous sequential approach as it doesn't increase nesting level.

Your environment

pirj commented 2 years ago

Thanks for reporting, this looks interesting.

Would you like to debug and pinpoint the issue? I'd start somewhere here. A pull request is welcome, too.

Side note:

it doesn't increase nesting level

It might be editor-specific. I never had problems in mine increasing indentation level.

Wondering if users of the other style https://rubystyle.guide/#consistent-multi-line-chains are affected by the growing indentation problem.

JonRowe commented 2 years ago

These are both (or should be) sequential in our terminology (how your editor/formatter formats it is not the issue we'd format both of those the same), its just that the ruby precedence changes and one chains of the and matcher, and one chains off the include matcher, why that makes a difference I don't know yet.

genehsu commented 2 years ago

I was able to replicate the problem with both expressions. I believe they both hit the underlying problem, but ruby may compile the expressions differently.

When expressed the first way, the match expression is compiled like this (AND (AND (AND X X) X) X).

#<RSpec::Matchers::BuiltIn::Compound::And:0x000056077b68bd88
  @matcher_1=#<RSpec::Matchers::BuiltIn::Compound::And:0x000056077b68be00
    @matcher_1=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>, 
    @matcher_2=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>>,
  @matcher_2=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>>

When expressed the second way, the match expression is compiled like this (AND X (AND X (AND X X))).

#<RSpec::Matchers::BuiltIn::Compound::And:0x000056077b68b978
  @matcher_1=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>,
  @matcher_2=#<RSpec::Matchers::BuiltIn::Compound::And:0x000056077b68b9a0
    @matcher_1=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>,
    @matcher_2=#<RSpec::Matchers::BuiltIn::Include:0x000056077b68bef0 @expecteds=[3]>>>