Evaluating shared example customisation block before shared block

On Aug 1, 2010, at 5:39 PM, Myron M. wrote:

Assuming that can work. I’ve taken a closer look and getting that to work would take some serious re-architecting that I’m not sure is a good idea.

Maybe I misunderstood you here, but I took this to refer to the
passing of parameters to the shared example group, as you
suggested…and it turns out this isn’t very hard at all:

Pass parameters given to #it_should_behave_like on to the shared exam… · myronmarston/rspec-core@c353bad · GitHub

If we do this, we should use module_exec for both blocks so they both
get the same arguments.

The one issue with this is that it uses #module_exec, which is not
available in ruby 1.8.6–so we’d have to find a way to implement it,
similar to how cucumber implements #instance_exec when it’s not
available:

http://github.com/aslakhellesoy/cucumber/blob/30d43767a7cffd1675e990115ac86c139e4ea3e0/lib/cucumber/core_ext/instance_exec.rb#L16-31

RSpec does that too :). Pretty sure it was Aslak that added that some
years back.

Yeah - I’m playing around with an implementation of module_exec, but it
doesn’t seem to work quite the same way as instance exec does. Not yet,
anyhow.

David

Seems like your mental model is that of a customization block being a subclass or re-opening of the shared block. What you say makes sense in that model, but that’s not the same model I have.

My mental model is indeed that the customization block is like a
subclass. I’m not sure where I got it–it’s just the intuitive way I
understood shared_examples_for and it_should_behave_like. But if no
one else shares this mental model, then there’s not much point in
making rspec work this way. I’m happy going with whatever the general
consensus is. Although, I do think that my mental model makes for
some interesting possibilities :).

Assuming that can work. I’ve taken a closer look and getting that to work would take some serious re-architecting that I’m not sure is a good idea.

Maybe I misunderstood you here, but I took this to refer to the
passing of parameters to the shared example group, as you
suggested…and it turns out this isn’t very hard at all:

The one issue with this is that it uses #module_exec, which is not
available in ruby 1.8.6–so we’d have to find a way to implement it,
similar to how cucumber implements #instance_exec when it’s not
available:

http://github.com/aslakhellesoy/cucumber/blob/30d43767a7cffd1675e990115ac86c139e4ea3e0/lib/cucumber/core_ext/instance_exec.rb#L16-31

Myron

If we do this, we should use module_exec for both blocks so they both get the same arguments.

I actually find the use of this to be a bit confusing:

[:foo, :bar].each do |arg|
it_should_behave_like “Something”, arg do |a|
# The value of the param is already bound to arg and now it’s
bound to a, too.
end
end

I suppose it may be useful in some situations, so I’m fine with it as
long as the implementation allows you to skip the |a|:

[:foo, :bar].each do |arg|
it_should_behave_like “Something”, arg do
# no need to declare the |a| parameter since we already have it in
arg.
# I find this to be less confusing.
end
end

Actually, I just discovered that ruby 1.8.7 actually added module_exec.

I should have mentioned that–my default ruby these days is 1.8.7, and
I found the same thing.

re: order of evaluation of blocks, I think I’m inclined to go one way one minute, and another the next. Somebody convince me of one or the other.

Maybe it would be useful to make a list of the things that are
possible one way, but not the other, and vice versa…and then compare
these lists. Which list has the more useful and commonly needed use
cases?

a) add such a thing for module_exec as well, though I haven’t quite figured out how that works yet.

This is my vote. And I’m willing to take a stab at it–if for no
other reason then it’ll help increase my understanding of ruby :).
I’m going to take a look at how rubinius implements this, and see what
ruby specs there are for it.

Myron

On Aug 1, 2010, at 5:12 PM, Ashley M. wrote:

[1,2,3].each do |n|

it_should_behave_like “blah”, :a => 1

how does :a => 1 get passed to the “options” block, as shared_block in the code is never called with arguments? Would this need another code change? (Apologies if I’m being thick, it’s late and I should probably go to bed, but I wanted to review this first…)

Actually, I just discovered that ruby 1.8.7 actually added module_exec.
What does this mean? It means this:

  def self.define_shared_group_method(new_name, report_label=nil)
    module_eval(<<-END_RUBY, __FILE__, __LINE__)
      def self.#{new_name}(name, *args, &customization_block)
        shared_block = world.shared_example_groups[name]
        raise "Could not find shared example group named 

#{name.inspect}" unless shared_block

        describe("#{report_label || "it should behave like"} 

#{name}") do
module_exec *args, &shared_block
module_exec *args, &customization_block if
customization_block
end
end
END_RUBY
end

What does that mean? It means this:

shared_examples_for “foo” do |a,b,c|
it “#{a} #{b} #{c}s” do
a.should do_something_with(b, c)
end
end

describe “something” do
it_behaves_like “foo”, 1, 2, 3
end

Ta da!!!

Two problems to solve at this point:

  1. order of evaluation of blocks
  2. what to do about ruby 1.8.6

re: order of evaluation of blocks, I think I’m inclined to go one way
one minute, and another the next. Somebody convince me of one or the
other.

re: 1.8.6, we’ve got a home-grown implementation of instance_exec that
runs in 1.8.6 (although I just discovered that it’s broken - fix coming
shortly). I could

a) add such a thing for module_exec as well, though I haven’t quite
figured out how that works yet.
b) only support parameterized shared groups in ruby 1.8.7 or better
c). the most drastic option, would be to drop support for 1.8.6
entirely, but I don’t think that’s really feasible yet.

Thoughts?

David

On Aug 01, 2010, at 11:52 pm, David C. wrote:

re: 1.8.6, we’ve got a home-grown implementation of instance_exec that runs in 1.8.6 (although I just discovered that it’s broken - fix coming shortly). I could

a) add such a thing for module_exec as well, though I haven’t quite figured out how that works yet.
b) only support parameterized shared groups in ruby 1.8.7 or better
c). the most drastic option, would be to drop support for 1.8.6 entirely, but I don’t think that’s really feasible yet.

Hmmm. If you’re working on a Rails project with RSpec 2 (which I’m not,
but I’m guessing that will be a very common case), you need 1.8.7
anyway, as Rails 3 won’t run on anything less. If you’re not using
Rails, I can’t imagine anyone starting a new project on 1.8.6 now. (All
my new stuff is on 1.9.2.)

Is 1.8.6 support in RSpec 2 really necessary? Any thoughts from
anyone?

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

OK, I tried to implement #module_exec on ruby 1.8.6, and here’s what I
came up with:

It works (at least in the sense that it allows the specs and features
I added in the previous commits in that branch to pass on ruby 1.8.6),
but I don’t think it’s correct. It just calls #instance_exec, but
instance_exec and module_exec are not the same (at least as I
understand them…). However, I based that implementation on rubinius
1.0.1’s:

http://github.com/evanphx/rubinius/blob/release-1.0.1/kernel/common/module.rb#L438-441

Backports (a library that implements features of later versions of
ruby in 1.8.6) implements it in a similar fashion:

Unfortunately, rubyspec doesn’t provide much help in defining how
module_exec should work:

http://github.com/rubyspec/rubyspec/blob/master/core/module/module_exec_spec.rb

I’d need some concrete examples demonstrating how module_exec should
work (as compared to instance_exec) to implement it correctly. My
understanding is that they are identical except in regards to method
definitions–def’s within an instance_exec define methods directly on
the Module object instance, whereas def’s within a module_exec define
instance methods in the module (i.e. methods that will be added to any
class that includes the module).

One side issue I discovered is that instance_exec is implemented in
rspec-expectations, but not rspec-core (which, incidentally, is why I
linked to the cucumber implementation; I grepped in rspec-core and
couldn’t find it). instance_exec is already used in rspec-core:

It needs to be implemented in rspec-core if you want rspec-core to be
able to be used on 1.8.6 without rspec-expectations.

Myron

On 2 Aug 2010, at 1:08 PM, David C. wrote:

But what about people who are, for what ever reasons, stuck with Ruby 1.8.6 and want to upgrade? Also, there are a few rspec-2 + rails-2 efforts in the works, and there will be a solution for this sometime this fall.

We need to support 1.8.6.

Ah fair enough. I didn’t imagine there were many people stuck in that
situation - I thought 1.8.6 was largely obsolete now. I also didn’t
know there were any RSpec-2/Rails-2 solutions in progress, I thought
that was going to be unsupported.


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Aug 2, 2010, at 4:49 AM, Ashley M. wrote:

On Aug 01, 2010, at 11:52 pm, David C. wrote:

re: 1.8.6, we’ve got a home-grown implementation of instance_exec that runs in 1.8.6 (although I just discovered that it’s broken - fix coming shortly). I could

a) add such a thing for module_exec as well, though I haven’t quite figured out how that works yet.
b) only support parameterized shared groups in ruby 1.8.7 or better
c). the most drastic option, would be to drop support for 1.8.6 entirely, but I don’t think that’s really feasible yet.

Hmmm. If you’re working on a Rails project with RSpec 2 (which I’m not, but I’m guessing that will be a very common case), you need 1.8.7 anyway, as Rails 3 won’t run on anything less. If you’re not using Rails, I can’t imagine anyone starting a new project on 1.8.6 now. (All my new stuff is on 1.9.2.)

But what about people who are, for what ever reasons, stuck with Ruby
1.8.6 and want to upgrade? Also, there are a few rspec-2 + rails-2
efforts in the works, and there will be a solution for this sometime
this fall.

We need to support 1.8.6.

On Aug 1, 2010, at 10:08 PM, Myron M. wrote:

OK, I tried to implement #module_exec on ruby 1.8.6, and here’s what I
came up with:

Implemented Module#module_exec for ruby 1.8.6. This is needed for th… · myronmarston/rspec-core@364f20e · GitHub

It works (at least in the sense that it allows the specs and features
I added in the previous commits in that branch to pass on ruby 1.8.6),
but I don’t think it’s correct.

If we’re not exposing this as an API, and we’re only using it in order
to support this feature in RSpec when running Ruby 1.8.6, and it solves
our problem, then I think it’s correct as it needs to be.

backports/lib/backports/1.8.7/module.rb at v1.18.1 · marcandre/backports · GitHub
the Module object instance, whereas def’s within a module_exec define
It needs to be implemented in rspec-core if you want rspec-core to be
able to be used on 1.8.6 without rspec-expectations.

Done:

On 2 Aug 2010, at 4:08 AM, Myron M. wrote:

Backports (a library that implements features of later versions of
ruby in 1.8.6) implements it in a similar fashion:

backports/lib/backports/1.8.7/module.rb at v1.18.1 · marcandre/backports · GitHub

Conceivably, RSpec 2 could depend on Backports under Ruby 1.8.6. It’s
not my opinion that it should (I don’t have one), but I’m interested to
know the implications.

Would that solve any design problems inside RSpec?

Would that cause any problems for users?

Would the problems solved outweigh the problems used?

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 2 Aug 2010, at 2:04 AM, Myron M. wrote:

long as the implementation allows you to skip the |a|:

[:foo, :bar].each do |arg|
it_should_behave_like “Something”, arg do

no need to declare the |a| parameter since we already have it in

arg.

I find this to be less confusing.

end
end

Agreed - requiring the block parameter to be declared in the host group
is putting the onus back on the user, not the author, of the shared
examples. I think we need a way to implement the second version.

Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 1 Aug 2010, at 11:52 PM, David C. wrote:

re: order of evaluation of blocks, I think I’m inclined to go one way one minute, and another the next. Somebody convince me of one or the other.

One thing that may help clear this up is: can anyone offer a concrete
example of where overriding a shared example group helper method would
be useful (and better than an alternative design)?

My gut feeling is that, as David suggested, being able to override these
is creating a class hierarchy of example groups. It feels to me
unnervingly like overriding private methods. I wait to be proved wrong
though, I just can’t think of an example myself of wanting to do this.

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Aug 2, 2010, at 7:52 AM, David C. wrote:

If we’re not exposing this as an API, and we’re only using it in order to support this feature in RSpec when running Ruby 1.8.6, and it solves our problem, then I think it’s correct as it needs to be.

These are all true except for the “and it solves our problem” part. It
almost does, but the one missing piece is that methods defined in the
shared group are not available to its examples:

shared_examples_for “thing” do
def thing; Thing.new; do
it “does something” do
thing.should do_something
end
end

My inclination is to get this feature out with explicit non-support for
1.8.6, and then add support for 1.8.6 if we can get this to work.
Working on that now - should be pushing some code (including Myron’s
contribution) later today.

Cheers,
David

On Aug 3, 2010, at 6:43 AM, Ashley M. wrote:

On Aug 03, 2010, at 12:22 pm, David C. wrote:

My inclination is to get this feature out with explicit non-support for 1.8.6, and then add support for 1.8.6 if we can get this to work. Working on that now - should be pushing some code (including Myron’s contribution) later today.

Do you have everything in place to finish this off? Happy to help out if you want me to do any more coding on this, but it sounds like you’ve figured out a solution. In which case I’ll sit back and see how the changes fit with the shared examples I’ve got so far…

Pushed:

Please do let me know if this works with what you’ve got.

The issue of the evaluation order is still up for grabs, but this now
supports params to shared groups in Ruby >= 1.8.7.

On Aug 03, 2010, at 12:22 pm, David C. wrote:

My inclination is to get this feature out with explicit non-support for 1.8.6, and then add support for 1.8.6 if we can get this to work. Working on that now - should be pushing some code (including Myron’s contribution) later today.

Do you have everything in place to finish this off? Happy to help out
if you want me to do any more coding on this, but it sounds like you’ve
figured out a solution. In which case I’ll sit back and see how the
changes fit with the shared examples I’ve got so far…

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Aug 3, 2010, at 4:35 PM, Ashley M. wrote:

  • I don’t like the word contract any more, at least not here. It needs a better name, probably something that would fit if you wrote a similar spec for ActiveRecord’s has_many.
    I actually like contract a lot. Maybe we’ll need
    alias_shared_examples_for_to :slight_smile:

define_method :entity_dot_new_collection_member do |*args|
entity.send(:“new_#{item_name}”, *args)
end

Not a big deal, but it’s not as readable as it was before. (Not that it was exactly large-print Winnie the Pooh to start with, given the abstract nature of the shared examples.)

This is just Ruby. It bugged me for a while too, but mostly because I
kept forgetting. Now I’m completely accustomed to it and def and
define_method seem quite the same to me.

Second, you can’t refer to described_class in the descriptions. I don’t know why I though you’d be able to, but it would be nice if it worked :slight_smile: (You can see the place where my failed attempt was, where I left <described_class>.)

This was a mis-alignment between names in the group and its examples
(example_group.describes == example.described_class), but is now fixed
(you can refer to described_class in both cases):

Finally, I realised something when I added another example. I should say though, that all this time, I was only using the shared examples with one collection on the entity, and I added another a few minutes ago just for fun, and it just worked… I like :slight_smile: But it raised a point about things that are common to all shared examples, and parameters to individual uses. In my example case, entity_class and entity are relevant to both of the “collection” shared example groups, but collection_name, item_name, class_name are parameters to the shared examples individually.

With the current setup, there’s no way to require that a host group provides eg entity_class.

shared_examples_for “foo” do
raise “gotta define entity_class” unless
public_instance_methods.map{|m|m.to_s).include?(“entity_class”)
end

And also, if it’s defined as a let in the host, you can’t use it in the descriptions in the shared example group (which you couldn’t before, of course).

Right - the only thing available to descriptions is going to be the
params you pass in.

So I think this solves 90% of the problems I had before, and is certainly a workable solution to the specs I’m trying to write. I’d love to hear your thoughts on the rest though.

The issue of the evaluation order is still up for grabs, but this now supports params to shared groups in Ruby >= 1.8.7.

Well, I deliberately didn’t check what order you ended up using! Whatever it is works for me now, although I guess future experiments could change that…

Thanks for all the feedback!

Cheers,
David

Ashley: thanks for posting the example. It’s nice to see how this all
fits together.

Re: RSpec 2 for ruby 1.8.6: I don’t see RSpec 2 as being all that
useful for Rails 2.x projects on ruby 1.8.6. However, it’s still very
important for gems. I just converted one of my projects (VCR[1]) to
RSpec 2, and VCR supports ruby 1.8.6, 1.8.7 and 1.9.1. If we remove
ruby 1.8.6 support from RSpec 2, I’d have to migrate back to RSpec 1.x
so that I can continue to run the spec suite on 1.8.6. I imagine
there will be plenty of other libraries that will want to upgrade to
using RSpec 2 after the final release, while still supporting 1.8.6.

Good news: I messed around with module_exec some more, and I think I
have a working implementation for 1.8.6[2]. This was complicated
enough that I wanted to work on it in isolation from RSpec; hence the
separate github project. We’ll probably want to re-organize it a bit
before merging it in, if it’s deemed “good enough” to work for our
needs. It has some specs that pass for module_exec on 1.8.7, and they
pass on 1.8.6 with my implementation, too. There may be cases where
it still doesn’t work quite right, though–feel free to fork, add
specs, etc.

Myron

[1] GitHub - myronmarston/vcr: This is no longer the canonical repo -- it has moved to https://github.com/vcr/vcr
[2] GitHub - myronmarston/module_exec: An implementation of module_exec for ruby 1.8.6

On 3 Aug 2010, at 12:50 PM, David C. wrote:

Pushed:

Pass parameters given to #it_should_behave_like on to the shared exam… · rspec/rspec-core@8430361 · GitHub
Raise error when passing params to shared example groups in Ruby 1.8.6. · rspec/rspec-core@3cea7b8 · GitHub

Awesomeness!

Please do let me know if this works with what you’ve got.

In general, yes, this is a massive improvement! I’ve realised some
things that never occurred to me before, though. Maybe you have some
thoughts…

I’ve put everything on a Gist[1] (which needs a few tweaks here and
there, but I think it’s a reasonably example). Notes:

  • DomainLib is my holding module for everything I’ve extracted out of
    the project source. Anything inside that is generic, analogous to eg
    ActiveRecord (eg Entity ↔ AR::Base)

  • I’ve only pasted the specs, and only the contract-based ones at that
    (the implementation is not very interesting, nor is the interaction
    spec).

  • I don’t like the word contract any more, at least not here. It needs
    a better name, probably something that would fit if you wrote a similar
    spec for ActiveRecord’s has_many.

Some things I ran into:

First, I found that you can’t use the block variables in local helper
methods. Because Ruby methods aren’t closures, I’ve had to replace
methods like:

def entity_dot_new_collection_member(*args)
entity.send(:“new_#{item_name}”, *args)
end

with:

define_method :entity_dot_new_collection_member do |*args|
entity.send(:“new_#{item_name}”, *args)
end

Not a big deal, but it’s not as readable as it was before. (Not that it
was exactly large-print Winnie the Pooh to start with, given the
abstract nature of the shared examples.)

Second, you can’t refer to described_class in the descriptions. I
don’t know why I though you’d be able to, but it would be nice if it
worked :slight_smile: (You can see the place where my failed attempt was, where I
left <described_class>.)

Finally, I realised something when I added another example. I should
say though, that all this time, I was only using the shared examples
with one collection on the entity, and I added another a few minutes ago
just for fun, and it just worked… I like :slight_smile: But it raised a point
about things that are common to all shared examples, and parameters to
individual uses. In my example case, entity_class and entity are
relevant to both of the “collection” shared example groups, but
collection_name, item_name, class_name are parameters to the
shared examples individually.

With the current setup, there’s no way to require that a host group
provides eg entity_class. And also, if it’s defined as a let in the
host, you can’t use it in the descriptions in the shared example group
(which you couldn’t before, of course).

So I think this solves 90% of the problems I had before, and is
certainly a workable solution to the specs I’m trying to write. I’d
love to hear your thoughts on the rest though.

The issue of the evaluation order is still up for grabs, but this now supports params to shared groups in Ruby >= 1.8.7.

Well, I deliberately didn’t check what order you ended up using!
Whatever it is works for me now, although I guess future experiments
could change that…

Cheers!
Ash

[1] entity_contract.rb · GitHub


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 4 Aug 2010, at 7:55 AM, Myron M. wrote:

Ashley: thanks for posting the example. It’s nice to see how this all
fits together.

Arguably it would have made more sense to post that example before,
rather than expecting you all to read my mind :slight_smile:

I’m pleased with how it’s working out so far. I need to write a lot
more of these to know though. I only got as far as this one example
before deciding to shave the shared example yak before moving on.

Re: RSpec 2 for ruby 1.8.6: I don’t see RSpec 2 as being all that
useful for Rails 2.x projects on ruby 1.8.6. However, it’s still very
important for gems. I just converted one of my projects (VCR[1]) to
RSpec 2, and VCR supports ruby 1.8.6, 1.8.7 and 1.9.1. If we remove
ruby 1.8.6 support from RSpec 2, I’d have to migrate back to RSpec 1.x
so that I can continue to run the spec suite on 1.8.6. I imagine
there will be plenty of other libraries that will want to upgrade to
using RSpec 2 after the final release, while still supporting 1.8.6.

Cool, if it’s possible to maintain 1.8.6 support in RSpec 2 then by all
means do so. I wasn’t aware that such a large amount of code needed to
run on 1.8.6, I assumed most had moved to 1.8.7. Doesn’t look like it
takes too much to monkeypatch 1.8.6 up to spec anyway.

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 4 Aug 2010, at 1:05 AM, David C. wrote:

I actually like contract a lot. Maybe we’ll need alias_shared_examples_for_to :slight_smile:

Haha, actually that gets +1 from me! Should I file a ticket? :slight_smile:

In general I like contract, I just wasn’t sure it was the right word for
this usage of shared examples.

Maybe I just need to reword the shared examples, to write something like
this:

it_satisfies_contract “container of”, :children, :child, Child.name

(Obviously, if I had an inflection library in place, you could drop the
last 2 args)

This is just Ruby. It bugged me for a while too, but mostly because I kept forgetting. Now I’m completely accustomed to it and def and define_method seem quite the same to me.

Maybe. Perhaps then Ruby needs a neater closure-based method syntax eg:

foo = 1

defc my_method(bar)
foo + bar
end

or some such…

This was a mis-alignment between names in the group and its examples (example_group.describes == example.described_class), but is now fixed (you can refer to described_class in both cases):Expose described_class to example group · rspec/rspec-core@b236a8d · GitHub

Works for me! Ta :slight_smile:

shared_examples_for “foo” do
raise “gotta define entity_class” unless public_instance_methods.map{|m|m.to_s).include?(“entity_class”)
end

Aye, I guess I’m just in love with DSLs…

If I feel the need I might write a simple DSL and see if it’s worth it.

And also, if it’s defined as a let in the host, you can’t use it in the descriptions in the shared example group (which you couldn’t before, of course).

Right - the only thing available to descriptions is going to be the params you pass in.

I have a feeling this will cause a misalignment, but maybe not. I’ll
work through some more practical examples and see how it plays out.

BTW any idea when the next beta will go out, so that this is in a
released gem? I’ve got it working, but I had no luck using Bundler’s
:path option so I ended up having to build and install the gems into my
project gemset. That’s probably just a RubyGems/Bundler issue though.

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran