Tommy
Tommy

Reputation: 8178

Speed up "sleep" in Ruby

In my codes, there are many places I purposely puts "sleep", just because the features are required to be so. However, when I do testing using RSpec or Cucumber, it becomes a nuisance because it takes so long to complete the tests.

Updated: thanks for some of the suggestions. However either stub or manually redefining might be cumbersome. What I imagine is just a tweak call to speed up "sleep" with a scale.

There is this gem timecop. It can speed up Time.now by a scale. If such idea can be applied to sleep, that would be great!

Reference: https://github.com/travisjeffery/timecop


Updated:sorry for late response, below may illustrate my situation:

class SomeClass
    def some_method
        sleep 15
        make_api_call_A
        sleep 45
        make_api_call_B
    end
end

describe SomeClass do
    before do
        Acceleration.speed_up(10) # speed time up to 10x
    end

    after do
        Acceleration.reset
    end

    if "should make two API calls" do
        subject.some_method
    end
end

As you can see there are two sleeps with different durations in some_method. What I prefer is, instead of waiting 60s, if I can speed up sleep time to 10x, I just have to wait for 6s. That fastens the testing process a lot.

Upvotes: 1

Views: 2621

Answers (5)

Sergio
Sergio

Reputation: 272

There is a solution which in my opinion is easier and doesn't add more dependencies to your project:

In the class, put the sleep time into a constant, for example:

WAITING_TIME = 45

Call sleep with this constant instead of a number:

sleep WAITING_TIME

In the test use stub_const from RSpec to change the time used for sleeping:

before do
  stub_const('MyClass::WAITING_TIME', 4.5)
end

That is all. You don't need to mock the sleep call, you don't need an additional gem for this, and RSpec will take care of stubbing the constant and resetting it back at the end of the test.

Upvotes: 1

Dmitry Lihachev
Dmitry Lihachev

Reputation: 504

If you want to scale sleep, you can override Kernel.sleep method this way:

module Acceleration
  mattr_accessor :sleep_scale

  def self.speed_up(scale)
    self.sleep_scale = scale
  end

  def self.reset
    self.sleep_scale = 1
  end

  reset
end

module Kernel
  alias :original_sleep :sleep

  def sleep(time)
    original_sleep(time / Acceleration.sleep_scale.to_f)
  end
end

class SomeClass
  def some_method
      sleep 15
      make_api_call_A
      sleep 45
      make_api_call_B
  end
end

describe SomeClass do
  before do
    Acceleration.speed_up(10) # speed time up to 10x
  end

  after do
    Acceleration.reset
  end

  it "should make two API calls" do
    subject.some_method
  end
end

Upvotes: 2

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84353

Monkey-Patch Kernel#sleep

Assuming that you really have a valid use case here, and you don't want to refactor your code to avoid hard-coded sleep values or disable sleeping altogether, you can simply monkey-patch Kernel#sleep within your specs to use a scaled factor. For example, to make sleep run ten times faster (e.g. for 1/10 the specified time):

module Kernel
  alias_method :old_sleep, :sleep
  def sleep seconds
    old_sleep(seconds * 0.1)
  end  
end

Benchmark Your Scaling

Once you've monkey-patched sleep, you can test that your scaling works with Benchmark#realtime:

require 'benchmark'

Benchmark.realtime { sleep 10 }.round 2
#=> 1.0

Benchmark.realtime { sleep 0.1 }.round 2
#=> 0.01

Upvotes: 0

Dmitry Lihachev
Dmitry Lihachev

Reputation: 504

You need stub sleep calls in your specs.

class SomeClass
  def some_method
    sleep 10
  end
end

describe SomeClass do
  before do
    subject.stub!(:sleep)
  end

  it "should call sleep" do
    subject.should_receive(:sleep).with(10)
    subject.some_method
  end
end

Upvotes: 4

sawa
sawa

Reputation: 168101

Temporarily add the following only when you test.

def sleep *; end

Upvotes: 1

Related Questions