Skip to content

Commit

Permalink
Initial.
Browse files Browse the repository at this point in the history
  • Loading branch information
danp committed Jun 16, 2011
0 parents commit f25ef64
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Gemfile
@@ -0,0 +1,3 @@
source :rubygems

gemspec
43 changes: 43 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,43 @@
PATH
remote: .
specs:
resque-mock (1.0.0)
resque

GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
json (1.5.1)
rack (1.3.0)
rake (0.9.2)
redis (2.2.1)
redis-namespace (1.0.3)
redis (< 3.0.0)
resque (1.17.1)
json (< 1.6, >= 1.4.6)
redis-namespace (~> 1.0.2)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.3)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
sinatra (1.2.6)
rack (~> 1.1)
tilt (< 2.0, >= 1.2.2)
tilt (1.3.2)
vegas (0.1.8)
rack (>= 1.0.0)

PLATFORMS
ruby

DEPENDENCIES
rake
resque-mock!
rspec
10 changes: 10 additions & 0 deletions Rakefile
@@ -0,0 +1,10 @@
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new do |t|
t.rspec_opts = %w[ -c ]
t.pattern = 'spec/**/*_spec.rb'
end

task :default => :spec
rescue LoadError
end
84 changes: 84 additions & 0 deletions lib/resque/mock.rb
@@ -0,0 +1,84 @@
require 'resque'

module Resque
def self.mock!
extend MockExt
end

module MockExt
def async
@async = true
create_worker_manager
yield
ensure
wait_for_worker_manager
@async = false
end

def enqueue(klass, *args)
puts "Mock enqueue: async=#{!!@async}, stack_depth=#{caller.size}, #{klass}, #{args.inspect}" if ENV['VERBOSE']
defer(klass, args)
end

def enqueue_in(delay, klass, *args)
puts "Mock enqueue in #{delay}: async=#{!!@async}, stack_depth=#{caller.size}, #{klass}, #{args.inspect}" if ENV['VERBOSE']
defer(klass, args, delay)
end

def defer(klass, args, delay = nil)
if @async
add_job('payload' => { 'class' => klass, 'args' => args }, 'delay' => delay)
else
sleep delay if delay
klass.perform(*roundtrip(args))
end
end

def create_worker_manager
@worker_manager = Thread.new do
Thread.current.abort_on_exception = true
worker_threads = []

while true
break if Thread.current[:exit] && worker_threads.empty? && Thread.current[:jobs].empty?

worker_threads.reject! {|t| !t.alive? }

while Thread.current[:jobs] && job_data = Thread.current[:jobs].shift
worker_threads << create_worker_thread_for(job_data)
end

sleep 0.5
end
end.tap {|t| t[:jobs] = [] }
end

def wait_for_worker_manager
@worker_manager[:exit] = true
@worker_manager.join
@worker_manager = nil
end

def create_worker_thread_for(data)
Thread.new(data) do |data|
Thread.current.abort_on_exception = true
if delay = data['delay']
sleep delay
end

klass = data['payload']['class']
puts "Mock perform: #{klass}.perform(*#{data['payload']['args'].inspect})" if ENV['VERBOSE']
klass.perform(*roundtrip(data['payload']['args']))
puts "Mock exit: #{klass}.perform(*#{data['payload']['args'].inspect})" if ENV['VERBOSE']
end
end

def roundtrip(args)
decode(encode(args))
end

def add_job(data)
@worker_manager[:jobs] << data
end
end
end
16 changes: 16 additions & 0 deletions resque-mock.gemspec
@@ -0,0 +1,16 @@
Gem::Specification.new do |gem|
gem.authors = ["Dan Peterson"]
gem.email = ["dpiddy@gmail.com"]
gem.description = gem.summary = %q{Mock resque with threads}
gem.homepage = 'https://github.com/dpiddy/resque-mock'

gem.files = ['lib/resque/mock.rb']
gem.test_files = ['Rakefile'] + Dir['spec/**.rb']
gem.name = "resque-mock"
gem.require_paths = ['lib']
gem.version = '0.1.0.pre'

gem.add_runtime_dependency 'resque'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rspec'
end
111 changes: 111 additions & 0 deletions spec/mock_spec.rb
@@ -0,0 +1,111 @@
#require 'spec_helper'
require 'resque/mock'

Resque.mock!

class Performer
def self.run?
!!@args
end

def self.args
@args
end

def self.runs
@runs || 0
end

def self.perform(*args)
@runs += 1
if Hash === (options = args.first)
if runs_left = options['runs']
runs_left -= 1
if runs_left > 0
Resque.enqueue(self, 'runs' => runs_left)
end
end
end
@args = args
end

def self.reset!
@args = nil
@runs = 0
end
end

class BadPerformer < Performer
def self.perform(*args)
raise 'hello'
end
end

describe Resque do
before { Performer.reset! }

describe "synchronously" do
it "performs jobs without delay" do
Resque.enqueue(Performer, 'hello', 'there')
Performer.should be_run
Performer.args.should == ['hello', 'there']
end

it "performs jobs with a delay" do
Resque.should_receive(:sleep).with(5)
Resque.enqueue_in(5, Performer, 'hello', 'there')
Performer.should be_run
Performer.args.should == ['hello', 'there']
end

it "can perform more jobs that are queued" do
Resque.enqueue(Performer, 'runs' => 3)
Performer.runs.should == 3
end

it "roundtrips arguments" do
Resque.enqueue(Performer, :hello => :there)
Performer.args.should == [{ 'hello' => 'there' }]
end
end

describe "asynchronously" do
it "performs jobs without delay" do
Resque.async do
Resque.enqueue(Performer, 'hello', 'there')
end

Performer.should be_run
Performer.args.should == ['hello', 'there']
end

it "performs jobs with delay" do
# not immediately sure how to mock this

Resque.async do
Resque.enqueue_in(5, Performer, 'hello', 'there')
end

Performer.should be_run
Performer.args.should == ['hello', 'there']
end

it "can perform more jobs that are queued" do
Resque.async { Resque.enqueue(Performer, 'runs' => 3) }
Performer.runs.should == 3
end

it "roundtrips arguments" do
Resque.async { Resque.enqueue(Performer, :hello => :there) }
Performer.args.should == [{ 'hello' => 'there' }]
end

it "raises errors encountered inside the block" do
expect { Resque.async { raise 'hello' } }.to raise_error
end

it "raises errors encountered by jobs" do
expect { Resque.async { Resque.enqueue(BadPerformer, 5) } }.to raise_error
end
end
end

0 comments on commit f25ef64

Please sign in to comment.