Hi all

I've recently become aware that  `expect { … }.to_not raise_error(SomeError)` 
has been deprecated. I've found a few cases where this has been frustrating, 
but I've hit one I'm completely stumped by. 

I'm writing a Celluloid actor which invokes some user-provided procs with 
(separately) user-provided values. As an example of something that might go 
wrong, the object providing a value may provide the wrong number of arguments 
for a given callback, and so an ArgumentError will be raised. Here's a cut-down 
version of the code:

    class Result
      include Celluloid

      # … lots of stuff omitted …

      def on(handlers)
        # … more stuff omitted …
        handlers.fetch(@message_type).call(*@message_args)
      end
    end

Normally, if an error is raised in an actor method, that actor is killed. As 
you can see, `call` is wide open to an ArgumentError. However, as this is the 
client object's fault, not ours, I don't want the actor to crash. Celluloid 
provides an `abort` method[1] for this purpose. My problem is in specifying the 
behaviour. Naively, you might try to check if the actor is still alive:

    it "doesn't crash the actor" do
      expect {
        actor.method_that_raises_an_error
      }.to_not change { actor.alive? }.to(false)
    end

Unfortunately, as the actor is running asynchronously, the example checks the 
actor's alive state before it completely dies. One way to approach this is to 
use Celluloid's actor guarantees and send it another message, which will be 
handled synchronously after the first is processed, and will raise a 
DeadActorError:

    it "doesn't crash the actor" do
      actor.method_that_raises_an_error rescue nil

      expect {
        actor.method_that_raises_an_error
      }.to_not raise_error(Celluloid::DeadActorError)
    end

Unfortunately, this prints a deprecation warning into the spec output, and 
presumably this will actually fail in a future version of RSpec.

Now, it's obvious from the code that changing the expectation to just `to_not 
raise_error()` is wrong, as we are expecting the method to raise an error – we 
just want to make sure it's not a Celluloid::DeadActorError. I could rescue 
specific errors inside the expect block, but now I'm duplicating knowledge from 
other examples just to make this one run.

So another way to approach this is my waiting on the actor's thread. Again, 
naively this is a starting point:

    it "doesn't crash the actor" do
      expect {
        actor.method_that_raises_an_error rescue nil
        Celluloid::Actor.join(actor)
      }.to_not change { actor.alive? }.to(false)
    end

There are two problems with this. The first is that it sets up a race 
condition, because any delay in the RSpec thread, eg:

    it "doesn't crash the actor" do
      expect {
        actor.method_that_raises_an_error rescue nil
        sleep 0.1
        Celluloid::Actor.join(actor)
      }.to_not change { actor.alive? }.to(false)
    end

Produces this error in Actor.join:

    An error occurred in an after hook
      Celluloid::DeadActorError: actor already terminated

Since the purpose of Celluloid / actors is to avoid this sort of problem, I 
don't consider it an acceptable approach.

The second (much bigger) problem, is that it only helps describe the failure 
case, once the actor is fixed to use `abort` rather than `raise`, it doesn't 
crash, and so Actor.join blocks the RSpec thread indefinitely.

The only option remaining I can think of is to re-implement the negative error 
expectation with begin/rescue, which is what I've done in other places I find 
this pattern useful.

Does anybody have any suggestions that may help? I've exhausted my own ideas.

Thanks
Ash


[1] 
https://github.com/celluloid/celluloid/wiki/Frequently-Asked-Questions#q-how-can-i-raise-an-exception-in-the-caller-without-crashing-the-receiver


-- 
http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashmoran

Attachment: smime.p7s
Description: S/MIME cryptographic signature

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to