aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activejob/.gitignore2
-rw-r--r--activejob/.travis.yml39
-rw-r--r--activejob/CHANGELOG.md1
-rw-r--r--activejob/Gemfile25
-rw-r--r--activejob/Gemfile.lock206
-rw-r--r--activejob/MIT-LICENSE21
-rw-r--r--activejob/README.md120
-rw-r--r--activejob/Rakefile68
-rw-r--r--activejob/activejob.gemspec21
-rw-r--r--activejob/lib/active_job.rb34
-rw-r--r--activejob/lib/active_job/arguments.rb52
-rw-r--r--activejob/lib/active_job/base.rb22
-rw-r--r--activejob/lib/active_job/callbacks.rb40
-rw-r--r--activejob/lib/active_job/enqueuing.rb71
-rw-r--r--activejob/lib/active_job/execution.rb27
-rw-r--r--activejob/lib/active_job/gem_version.rb15
-rw-r--r--activejob/lib/active_job/identifier.rb15
-rw-r--r--activejob/lib/active_job/logging.rb88
-rw-r--r--activejob/lib/active_job/queue_adapter.rb24
-rw-r--r--activejob/lib/active_job/queue_adapters/backburner_adapter.rb25
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb15
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb23
-rw-r--r--activejob/lib/active_job/queue_adapters/resque_adapter.rb38
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb35
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb34
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb25
-rw-r--r--activejob/lib/active_job/queue_name.rb18
-rw-r--r--activejob/lib/active_job/railtie.rb10
-rw-r--r--activejob/lib/active_job/version.rb8
-rw-r--r--activejob/lib/activejob.rb1
-rw-r--r--activejob/lib/rails/generators/active_job/job_generator.rb22
-rw-r--r--activejob/lib/rails/generators/active_job/templates/job.rb9
-rw-r--r--activejob/test/adapters/backburner.rb3
-rw-r--r--activejob/test/adapters/delayed_job.rb7
-rw-r--r--activejob/test/adapters/inline.rb1
-rw-r--r--activejob/test/adapters/qu.rb3
-rw-r--r--activejob/test/adapters/que.rb2
-rw-r--r--activejob/test/adapters/queue_classic.rb2
-rw-r--r--activejob/test/adapters/resque.rb2
-rw-r--r--activejob/test/adapters/sidekiq.rb2
-rw-r--r--activejob/test/adapters/sneakers.rb2
-rw-r--r--activejob/test/adapters/sucker_punch.rb2
-rw-r--r--activejob/test/cases/adapter_test.rb56
-rw-r--r--activejob/test/cases/callbacks_test.rb22
-rw-r--r--activejob/test/cases/job_serialization_test.rb15
-rw-r--r--activejob/test/cases/logging_test.rb94
-rw-r--r--activejob/test/cases/parameters_test.rb77
-rw-r--r--activejob/test/cases/queue_naming_test.rb21
-rw-r--r--activejob/test/cases/queuing_test.rb44
-rw-r--r--activejob/test/cases/rescue_test.rb23
-rw-r--r--activejob/test/dummy/Rakefile3
-rw-r--r--activejob/test/dummy/app/assets/images/.keep0
-rw-r--r--activejob/test/dummy/app/controllers/application_controller.rb3
-rw-r--r--activejob/test/dummy/app/controllers/concerns/.keep0
-rw-r--r--activejob/test/dummy/app/helpers/application_helper.rb2
-rw-r--r--activejob/test/dummy/app/jobs/test_job.rb9
-rw-r--r--activejob/test/dummy/app/mailers/.keep0
-rw-r--r--activejob/test/dummy/app/models/.keep0
-rw-r--r--activejob/test/dummy/app/models/concerns/.keep0
-rw-r--r--activejob/test/dummy/config.ru4
-rw-r--r--activejob/test/dummy/config/application.rb9
-rw-r--r--activejob/test/dummy/config/boot.rb5
-rw-r--r--activejob/test/dummy/config/database.yml3
-rw-r--r--activejob/test/dummy/config/environment.rb2
-rw-r--r--activejob/test/dummy/config/environments/test.rb13
-rw-r--r--activejob/test/dummy/config/initializers/activejob.rb65
-rw-r--r--activejob/test/dummy/config/initializers/session_store.rb1
-rw-r--r--activejob/test/dummy/config/routes.rb2
-rw-r--r--activejob/test/dummy/config/secrets.yml2
-rw-r--r--activejob/test/dummy/db/migrate/20140804200445_create_delayed_jobs.rb22
-rw-r--r--activejob/test/dummy/db/schema.rb32
-rw-r--r--activejob/test/dummy/db/test.sqlite3bin0 -> 24576 bytes
-rw-r--r--activejob/test/dummy/lib/assets/.keep0
-rw-r--r--activejob/test/dummy/log/.keep0
-rw-r--r--activejob/test/dummy/tmp/.keep0
-rw-r--r--activejob/test/helper.rb19
-rw-r--r--activejob/test/integration/queuing_test.rb18
-rw-r--r--activejob/test/jobs/callback_job.rb32
-rw-r--r--activejob/test/jobs/gid_job.rb6
-rw-r--r--activejob/test/jobs/hello_job.rb5
-rw-r--r--activejob/test/jobs/logging_job.rb10
-rw-r--r--activejob/test/jobs/nested_job.rb10
-rw-r--r--activejob/test/jobs/rescue_job.rb20
-rw-r--r--activejob/test/models/person.rb19
-rw-r--r--activejob/test/support/backburner/inline.rb8
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb113
-rw-r--r--activejob/test/support/delayed_job/delayed/serialization/test.rb0
-rw-r--r--activejob/test/support/integration/adapters/backburner.rb15
-rw-r--r--activejob/test/support/integration/adapters/delayed_job.rb14
-rw-r--r--activejob/test/support/integration/adapters/qu.rb14
-rw-r--r--activejob/test/support/integration/adapters/que.rb19
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb21
-rw-r--r--activejob/test/support/integration/adapters/resque.rb18
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb19
-rw-r--r--activejob/test/support/integration/adapters/sneakers.rb18
-rw-r--r--activejob/test/support/integration/adapters/sucker_punch.rb5
-rw-r--r--activejob/test/support/integration/helper.rb12
-rw-r--r--activejob/test/support/integration/jobs_manager.rb23
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb30
-rw-r--r--activejob/test/support/queue_classic/inline.rb11
-rw-r--r--activejob/test/support/sneakers/inline.rb12
104 files changed, 2276 insertions, 0 deletions
diff --git a/activejob/.gitignore b/activejob/.gitignore
new file mode 100644
index 0000000000..ee45263e91
--- /dev/null
+++ b/activejob/.gitignore
@@ -0,0 +1,2 @@
+/test/dummy/log/*
+/test/dummy/tmp/*
diff --git a/activejob/.travis.yml b/activejob/.travis.yml
new file mode 100644
index 0000000000..fba12f9d0d
--- /dev/null
+++ b/activejob/.travis.yml
@@ -0,0 +1,39 @@
+before_install:
+- travis_retry gem install bundler
+- sudo apt-get update -qq
+- sudo apt-get install beanstalkd
+- echo "START=yes" | sudo tee -a /etc/default/beanstalkd
+- sudo /etc/init.d/beanstalkd start
+
+rvm:
+- 1.9.3
+- 2.0.0
+- 2.1
+- ruby-head
+- rbx-2
+- jruby
+env:
+- QC_DATABASE_URL="postgres://postgres@localhost/active_jobs_qc_int_test" QUE_DATABASE_URL="postgres://postgres@localhost/active_jobs_qc_int_test"
+matrix:
+ allow_failures:
+ - rvm: rbx-2
+ - rvm: jruby
+ - rvm: ruby-head
+ fast_finish: true
+notifications:
+ email: false
+ irc:
+ on_success: change
+ on_failure: always
+ channels:
+ - irc.freenode.org#rails-contrib
+ campfire:
+ on_success: change
+ on_failure: always
+ rooms:
+ - secure: AgZwJA+9VdnWAw7QN9Z5s6RpQIzsEB0q7V+p3pCzXY45156WocL8iNQx+KnyOQ8jbRUt4L/XIOiZl5xHf4pHjXytHWHNhetAlVQP/hPeDcCSk/h0g5gqgf6QABdp38tBNaUq866bXHgCOZYPwwP9bypcmuv2SLyfIO+b/PBgqN0=
+services:
+- redis
+- rabbitmq
+addons:
+ postgresql: "9.3"
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
new file mode 100644
index 0000000000..b04883413e
--- /dev/null
+++ b/activejob/CHANGELOG.md
@@ -0,0 +1 @@
+* Started project. \ No newline at end of file
diff --git a/activejob/Gemfile b/activejob/Gemfile
new file mode 100644
index 0000000000..12a1798fd5
--- /dev/null
+++ b/activejob/Gemfile
@@ -0,0 +1,25 @@
+source 'https://rubygems.org'
+
+gemspec
+
+gem 'rake'
+gem 'resque'
+gem 'resque-scheduler'
+gem 'sidekiq'
+gem 'sucker_punch'
+gem 'delayed_job'
+gem 'queue_classic'
+gem 'sneakers', '0.1.1.pre'
+gem 'que'
+gem 'backburner'
+gem 'qu-rails', github: "bkeepers/qu", branch: "master"
+gem 'qu-redis'
+
+#for integration testing
+gem 'arel', github: 'rails/arel'
+gem 'rack', github: 'rack/rack'
+gem 'i18n', github: 'svenfuchs/i18n'
+gem 'rails', github: 'rails/rails'
+gem 'sqlite3'
+gem 'delayed_job_active_record'
+gem 'sequel'
diff --git a/activejob/Gemfile.lock b/activejob/Gemfile.lock
new file mode 100644
index 0000000000..53ba5c055b
--- /dev/null
+++ b/activejob/Gemfile.lock
@@ -0,0 +1,206 @@
+GIT
+ remote: git://github.com/bkeepers/qu.git
+ revision: 50f3788f2b55ddd4dc939767fb35aebefa260322
+ branch: master
+ specs:
+ qu (0.2.0)
+ qu-rails (0.2.0)
+ qu (= 0.2.0)
+ railties (>= 3.2, < 5)
+ qu-redis (0.2.0)
+ qu (= 0.2.0)
+ redis-namespace
+
+GIT
+ remote: git://github.com/rack/rack.git
+ revision: e98a9f7ef0ddd9589145ea953948c73a8ce3caa9
+ specs:
+ rack (1.6.0.alpha)
+
+GIT
+ remote: git://github.com/rails/arel.git
+ revision: 66cee768bc163537087037a583f60639eae49fc3
+ specs:
+ arel (6.0.0.20140505020427)
+
+GIT
+ remote: git://github.com/rails/rails.git
+ revision: b2e88043b52a8f83820a0f4e8a65aa42fd40c544
+ specs:
+ actionmailer (4.2.0.alpha)
+ actionpack (= 4.2.0.alpha)
+ actionview (= 4.2.0.alpha)
+ mail (~> 2.5, >= 2.5.4)
+ actionpack (4.2.0.alpha)
+ actionview (= 4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ rack (~> 1.6.0.alpha)
+ rack-test (~> 0.6.2)
+ actionview (4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ activemodel (4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ builder (~> 3.1)
+ activerecord (4.2.0.alpha)
+ activemodel (= 4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ arel (~> 6.0.0)
+ activesupport (4.2.0.alpha)
+ i18n (>= 0.7.0.dev, < 0.8)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.1)
+ tzinfo (~> 1.1)
+ rails (4.2.0.alpha)
+ actionmailer (= 4.2.0.alpha)
+ actionpack (= 4.2.0.alpha)
+ actionview (= 4.2.0.alpha)
+ activemodel (= 4.2.0.alpha)
+ activerecord (= 4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 4.2.0.alpha)
+ sprockets-rails (~> 2.1)
+ railties (4.2.0.alpha)
+ actionpack (= 4.2.0.alpha)
+ activesupport (= 4.2.0.alpha)
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+
+GIT
+ remote: git://github.com/svenfuchs/i18n.git
+ revision: cb679b8cdbab675703a3f88de4d48a48f7b50e06
+ specs:
+ i18n (0.7.0.dev)
+
+PATH
+ remote: .
+ specs:
+ activejob (4.2.0.alpha)
+ activemodel-globalid
+ activesupport (>= 4.1.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ activemodel-globalid (0.1.1)
+ activemodel (>= 4.1.0)
+ activesupport (>= 4.1.0)
+ amq-protocol (1.9.2)
+ backburner (0.4.5)
+ beaneater (~> 0.3.1)
+ dante (~> 0.1.5)
+ beaneater (0.3.2)
+ builder (3.2.2)
+ bunny (1.1.9)
+ amq-protocol (>= 1.9.2)
+ celluloid (0.15.2)
+ timers (~> 1.1.0)
+ connection_pool (2.0.0)
+ dante (0.1.5)
+ delayed_job (4.0.2)
+ activesupport (>= 3.0, < 4.2)
+ delayed_job_active_record (4.0.1)
+ activerecord (>= 3.0, < 4.2)
+ delayed_job (>= 3.0, < 4.1)
+ erubis (2.7.0)
+ hike (1.2.3)
+ json (1.8.1)
+ mail (2.6.1)
+ mime-types (>= 1.16, < 3)
+ mime-types (2.3)
+ minitest (5.4.0)
+ mono_logger (1.1.0)
+ multi_json (1.10.1)
+ pg (0.17.1)
+ que (0.8.1)
+ queue_classic (2.2.3)
+ pg (~> 0.17.0)
+ rack-protection (1.5.3)
+ rack
+ rack-test (0.6.2)
+ rack (>= 1.0)
+ rake (10.3.2)
+ redis (3.1.0)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
+ resque (1.25.2)
+ mono_logger (~> 1.0)
+ multi_json (~> 1.0)
+ redis-namespace (~> 1.3)
+ sinatra (>= 0.9.2)
+ vegas (~> 0.1.2)
+ resque-scheduler (3.0.0)
+ mono_logger (~> 1.0)
+ redis (~> 3.0)
+ resque (~> 1.25)
+ rufus-scheduler (~> 2.0)
+ rufus-scheduler (2.0.24)
+ tzinfo (>= 0.3.22)
+ sequel (4.8.0)
+ serverengine (1.5.9)
+ sigdump (~> 0.2.2)
+ sidekiq (3.2.1)
+ celluloid (>= 0.15.2)
+ connection_pool (>= 2.0.0)
+ json
+ redis (>= 3.0.6)
+ redis-namespace (>= 1.3.1)
+ sigdump (0.2.2)
+ sinatra (1.4.5)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ sneakers (0.1.1.pre)
+ bunny (~> 1.1.3)
+ serverengine
+ thor
+ thread
+ sprockets (2.12.1)
+ hike (~> 1.2)
+ multi_json (~> 1.0)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sprockets-rails (2.1.3)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ sprockets (~> 2.8)
+ sqlite3 (1.3.9)
+ sucker_punch (1.1)
+ celluloid (~> 0.15.2)
+ thor (0.19.1)
+ thread (0.1.4)
+ thread_safe (0.3.4)
+ tilt (1.4.1)
+ timers (1.1.0)
+ tzinfo (1.2.1)
+ thread_safe (~> 0.1)
+ vegas (0.1.11)
+ rack (>= 1.0.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ activejob!
+ arel!
+ backburner
+ delayed_job
+ delayed_job_active_record
+ i18n!
+ qu-rails!
+ qu-redis
+ que
+ queue_classic
+ rack!
+ rails!
+ rake
+ resque
+ resque-scheduler
+ sequel
+ sidekiq
+ sneakers (= 0.1.1.pre)
+ sqlite3
+ sucker_punch
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
new file mode 100644
index 0000000000..8b1e97b776
--- /dev/null
+++ b/activejob/MIT-LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2014 David Heinemeier Hansson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/activejob/README.md b/activejob/README.md
new file mode 100644
index 0000000000..5adb41b0b6
--- /dev/null
+++ b/activejob/README.md
@@ -0,0 +1,120 @@
+# Active Job -- Make work happen later
+
+Active Job is a framework for declaring jobs and making them run on a variety
+of queueing backends. These jobs can be everything from regularly scheduled
+clean-ups, billing charges, or mailings. Anything that can be chopped up into
+small units of work and run in parallel, really.
+
+It also serves as the backend for [ActionMailer's #deliver_later functionality](https://github.com/rails/activejob/issues/13)
+that makes it easy to turn any mailing into a job for running later. That's
+one of the most common jobs in a modern web application: Sending emails outside
+of the request-response cycle, so the user doesn't have to wait on it.
+
+The main point is to ensure that all Rails apps will have a job infrastructure
+in place, even if it's in the form of an "immediate runner". We can then have
+framework features and other gems build on top of that, without having to worry
+about API differences between Delayed Job and Resque. Picking your queuing
+backend becomes more of an operational concern, then. And you'll be able to
+switch between them without having to rewrite your jobs.
+
+
+## Usage
+
+Set the queue adapter for Active Job:
+
+``` ruby
+ActiveJob::Base.queue_adapter = :inline # default queue adapter
+# Adapters currently supported: :backburner, :delayed_job, :qu, :que, :queue_classic,
+# :resque, :sidekiq, :sneakers, :sucker_punch
+```
+
+Declare a job like so:
+
+```ruby
+class MyJob < ActiveJob::Base
+ queue_as :my_jobs
+
+ def perform(record)
+ record.do_work
+ end
+end
+```
+
+Enqueue a job like so:
+
+```ruby
+MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free.
+```
+
+```ruby
+MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
+```
+
+```ruby
+MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
+```
+
+That's it!
+
+
+## GlobalID support
+
+Active Job supports [GlobalID serialization](https://github.com/rails/activemodel-globalid/) for parameters. This makes it possible
+to pass live Active Record objects to your job instead of class/id pairs, which
+you then have to manually deserialize. Before, jobs would look like this:
+
+```ruby
+class TrashableCleanupJob
+ def perform(trashable_class, trashable_id, depth)
+ trashable = trashable_class.constantize.find(trashable_id)
+ trashable.cleanup(depth)
+ end
+end
+```
+
+Now you can simply do:
+
+```ruby
+class TrashableCleanupJob
+ def perform(trashable, depth)
+ trashable.cleanup(depth)
+ end
+end
+```
+
+This works with any class that mixes in ActiveModel::GlobalIdentification, which
+by default has been mixed into Active Record classes.
+
+
+## Supported queueing systems
+
+We currently have adapters for:
+
+* [Backburner](https://github.com/nesquena/backburner)
+* [Delayed Job](https://github.com/collectiveidea/delayed_job)
+* [Qu](https://github.com/bkeepers/qu)
+* [Que](https://github.com/chanks/que)
+* [QueueClassic](https://github.com/ryandotsmith/queue_classic)
+* [Resque 1.x](https://github.com/resque/resque)
+* [Sidekiq](https://github.com/mperham/sidekiq)
+* [Sneakers](https://github.com/jondot/sneakers)
+* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
+
+
+## Auxiliary gems
+
+* [activejob-stats](https://github.com/seuros/activejob-stats)
+
+## Under development as a gem, targeted for Rails inclusion
+
+Active Job is currently being developed in a separate repository until it's
+ready to be merged in with Rails. The current plan is to have Active Job
+be part of the Rails 4.2 release, but plans may change depending on when
+this framework stabilizes and feels ready.
+
+
+## License
+
+Active Job is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
diff --git a/activejob/Rakefile b/activejob/Rakefile
new file mode 100644
index 0000000000..b2910de81d
--- /dev/null
+++ b/activejob/Rakefile
@@ -0,0 +1,68 @@
+require 'bundler/gem_tasks'
+
+require 'rake/testtask'
+
+def run_without_aborting(*tasks)
+ errors = []
+
+ tasks.each do |task|
+ begin
+ Rake::Task[task].invoke
+ rescue Exception
+ errors << task
+ end
+ end
+
+ abort "Errors running #{errors.join(', ')}" if errors.any?
+end
+
+task default: :test
+
+ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
+
+desc 'Run all adapter tests'
+task :test do
+ tasks = ADAPTERS.map{|a| "test_#{a}" }+["integration_test"]
+ run_without_aborting(*tasks)
+end
+
+ADAPTERS.each do |adapter|
+ Rake::TestTask.new("test_#{adapter}") do |t|
+ t.libs << 'test'
+ t.test_files = FileList['test/cases/**/*_test.rb']
+ t.verbose = true
+ end
+
+ namespace adapter do
+ task test: "test_#{adapter}"
+ task(:env) { ENV['AJADAPTER'] = adapter }
+ end
+
+ task "test_#{adapter}" => "#{adapter}:env"
+end
+
+
+
+desc 'Run all adapter integration tests'
+task :integration_test do
+ tasks = (ADAPTERS-['inline']).map{|a| "integration_test_#{a}" }
+ run_without_aborting(*tasks)
+end
+
+(ADAPTERS-['inline']).each do |adapter|
+ Rake::TestTask.new("integration_test_#{adapter}") do |t|
+ t.libs << 'test'
+ t.test_files = FileList['test/integration/**/*_test.rb']
+ t.verbose = true
+ end
+
+ namespace "integration_#{adapter}" do
+ task test: "integration_test_#{adapter}"
+ task(:env) do
+ ENV['AJADAPTER'] = adapter
+ ENV['AJ_INTEGRATION_TESTS'] = "1"
+ end
+ end
+
+ task "integration_test_#{adapter}" => "integration_#{adapter}:env"
+end
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
new file mode 100644
index 0000000000..23fd25c6c8
--- /dev/null
+++ b/activejob/activejob.gemspec
@@ -0,0 +1,21 @@
+Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = 'activejob'
+ s.version = '4.2.0.alpha'
+ s.summary = 'Job framework with pluggable queues (will be part of Rails).'
+ s.description = 'Declare job classes that can be run by a variety of queueing backends.'
+
+ s.required_ruby_version = '>= 1.9.3'
+
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
+
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
+ s.require_path = 'lib'
+
+ s.add_dependency 'activesupport', '>= 4.1.0'
+ s.add_dependency 'activemodel-globalid'
+end
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
new file mode 100644
index 0000000000..ddfdda4fb4
--- /dev/null
+++ b/activejob/lib/active_job.rb
@@ -0,0 +1,34 @@
+#--
+# Copyright (c) 2014 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+require 'active_support'
+require 'active_support/rails'
+
+require 'active_job/railtie' if defined?(Rails)
+require 'active_job/version'
+
+module ActiveJob
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
new file mode 100644
index 0000000000..ef6a3fce1d
--- /dev/null
+++ b/activejob/lib/active_job/arguments.rb
@@ -0,0 +1,52 @@
+require 'active_model/global_locator'
+require 'active_model/global_identification'
+
+module ActiveJob
+ class Arguments
+ TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
+
+ def self.serialize(arguments)
+ arguments.map { |argument| serialize_argument(argument) }
+ end
+
+ def self.deserialize(arguments)
+ arguments.map { |argument| deserialize_argument(argument) }
+ end
+
+ private
+ def self.serialize_argument(argument)
+ case argument
+ when ActiveModel::GlobalIdentification
+ argument.global_id.to_s
+ when *TYPE_WHITELIST
+ argument
+ when Array
+ serialize(argument)
+ when Hash
+ Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
+ else
+ raise "Unsupported argument type: #{argument.class.name}"
+ end
+ end
+
+ def self.deserialize_argument(argument)
+ case argument
+ when Array
+ deserialize(argument)
+ when Hash
+ Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
+ else
+ ActiveModel::GlobalLocator.locate(argument) || argument
+ end
+ end
+
+ def self.serialize_hash_key(key)
+ case key
+ when String, Symbol
+ key.to_s
+ else
+ raise "Unsupported hash key type: #{key.class.name}"
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
new file mode 100644
index 0000000000..d5ba253016
--- /dev/null
+++ b/activejob/lib/active_job/base.rb
@@ -0,0 +1,22 @@
+require 'active_job/queue_adapter'
+require 'active_job/queue_name'
+require 'active_job/enqueuing'
+require 'active_job/execution'
+require 'active_job/callbacks'
+require 'active_job/identifier'
+require 'active_job/logging'
+
+module ActiveJob
+ class Base
+ extend QueueAdapter
+
+ include QueueName
+ include Enqueuing
+ include Execution
+ include Callbacks
+ include Identifier
+ include Logging
+
+ ActiveSupport.run_load_hooks(:active_job, self)
+ end
+end
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
new file mode 100644
index 0000000000..c69e4a3b55
--- /dev/null
+++ b/activejob/lib/active_job/callbacks.rb
@@ -0,0 +1,40 @@
+require 'active_support/callbacks'
+
+module ActiveJob
+ module Callbacks
+ extend ActiveSupport::Concern
+ include ActiveSupport::Callbacks
+
+ included do
+ define_callbacks :perform
+ define_callbacks :enqueue
+ end
+
+ module ClassMethods
+ def before_perform(*filters, &blk)
+ set_callback(:perform, :before, *filters, &blk)
+ end
+
+ def after_perform(*filters, &blk)
+ set_callback(:perform, :after, *filters, &blk)
+ end
+
+ def around_perform(*filters, &blk)
+ set_callback(:perform, :around, *filters, &blk)
+ end
+
+
+ def before_enqueue(*filters, &blk)
+ set_callback(:enqueue, :before, *filters, &blk)
+ end
+
+ def after_enqueue(*filters, &blk)
+ set_callback(:enqueue, :after, *filters, &blk)
+ end
+
+ def around_enqueue(*filters, &blk)
+ set_callback(:enqueue, :around, *filters, &blk)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
new file mode 100644
index 0000000000..e3ac11ba97
--- /dev/null
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -0,0 +1,71 @@
+require 'active_job/arguments'
+
+module ActiveJob
+ module Enqueuing
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Push a job onto the queue. The arguments must be legal JSON types
+ # (string, int, float, nil, true, false, hash or array) or
+ # ActiveModel::GlobalIdentication instances. Arbitrary Ruby objects
+ # are not supported.
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments.
+ def enqueue(*args)
+ new(args).tap do |job|
+ job.run_callbacks :enqueue do
+ queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
+ end
+ end
+ end
+
+ # Enqueue a job to be performed at +interval+ from now.
+ #
+ # enqueue_in(1.week, "mike")
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments and the timestamp in Job#enqueue_at.
+ def enqueue_in(interval, *args)
+ enqueue_at interval.seconds.from_now, *args
+ end
+
+ # Enqueue a job to be performed at an explicit point in time.
+ #
+ # enqueue_at(Date.tomorrow.midnight, "mike")
+ #
+ # Returns an instance of the job class queued with args available in
+ # Job#arguments and the timestamp in Job#enqueue_at.
+ def enqueue_at(timestamp, *args)
+ new(args).tap do |job|
+ job.enqueued_at = timestamp
+
+ job.run_callbacks :enqueue do
+ queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
+ end
+ end
+ end
+ end
+
+ included do
+ attr_accessor :arguments
+ attr_accessor :enqueued_at
+ end
+
+ def initialize(arguments = nil)
+ @arguments = arguments
+ end
+
+ def retry_now
+ self.class.enqueue *arguments
+ end
+
+ def retry_in(interval)
+ self.class.enqueue_in interval, *arguments
+ end
+
+ def retry_at(timestamp)
+ self.class.enqueue_at timestamp, *arguments
+ end
+ end
+end
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
new file mode 100644
index 0000000000..78ada3d908
--- /dev/null
+++ b/activejob/lib/active_job/execution.rb
@@ -0,0 +1,27 @@
+require 'active_support/rescuable'
+require 'active_job/arguments'
+
+module ActiveJob
+ module Execution
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Rescuable
+ end
+
+ def execute(job_id, *serialized_args)
+ self.job_id = job_id
+ self.arguments = Arguments.deserialize(serialized_args)
+
+ run_callbacks :perform do
+ perform *arguments
+ end
+ rescue => exception
+ rescue_with_handler(exception) || raise(exception)
+ end
+
+ def perform(*)
+ raise NotImplementedError
+ end
+ end
+end
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
new file mode 100644
index 0000000000..c166020b28
--- /dev/null
+++ b/activejob/lib/active_job/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveJob
+ # Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activejob/lib/active_job/identifier.rb b/activejob/lib/active_job/identifier.rb
new file mode 100644
index 0000000000..c7f522087d
--- /dev/null
+++ b/activejob/lib/active_job/identifier.rb
@@ -0,0 +1,15 @@
+require 'active_job/arguments'
+
+module ActiveJob
+ module Identifier
+ extend ActiveSupport::Concern
+
+ included do
+ attr_writer :job_id
+ end
+
+ def job_id
+ @job_id ||= SecureRandom.uuid
+ end
+ end
+end
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
new file mode 100644
index 0000000000..d913aee03d
--- /dev/null
+++ b/activejob/lib/active_job/logging.rb
@@ -0,0 +1,88 @@
+require 'active_support/core_ext/string/filters'
+
+module ActiveJob
+ module Logging
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
+
+ around_enqueue do |job, block, _|
+ tag_logger do
+ block.call
+ end
+ end
+
+ around_perform do |job, block, _|
+ tag_logger(job.class.name, job.job_id) do
+ payload = {adapter: job.class.queue_adapter, job: job.class, args: job.arguments}
+ ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
+ ActiveSupport::Notifications.instrument("perform.active_job", payload) do |payload|
+ block.call
+ end
+ end
+ end
+
+ before_enqueue do |job|
+ if job.enqueued_at
+ ActiveSupport::Notifications.instrument "enqueue_at.active_job",
+ adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments, timestamp: job.enqueued_at
+ else
+ ActiveSupport::Notifications.instrument "enqueue.active_job",
+ adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments
+ end
+ end
+ end
+
+ private
+ def tag_logger(*tags)
+ if logger.respond_to?(:tagged)
+ tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
+ ActiveJob::Base.logger.tagged(*tags){ yield }
+ else
+ yield
+ end
+ end
+
+ def logger_tagged_by_active_job?
+ logger.formatter.current_tags.include?("ActiveJob")
+ end
+
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def enqueue(event)
+ info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event)
+ end
+
+ def enqueue_at(event)
+ info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event)
+ end
+
+ def perform_start(event)
+ info "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event)
+ end
+
+ def perform(event)
+ info "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
+ end
+
+ private
+ def queue_name(event)
+ event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
+ end
+
+ def args_info(event)
+ event.payload[:args].any? ? " with arguments: #{event.payload[:args].map(&:inspect).join(", ")}" : ""
+ end
+
+ def enqueued_at(event)
+ Time.at(event.payload[:timestamp]).utc
+ end
+
+ def logger
+ ActiveJob::Base.logger
+ end
+ end
+ end
+end
+
+ActiveJob::Logging::LogSubscriber.attach_to :active_job
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
new file mode 100644
index 0000000000..8f2f8b86ea
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -0,0 +1,24 @@
+require 'active_job/queue_adapters/inline_adapter'
+require 'active_support/core_ext/string/inflections'
+
+module ActiveJob
+ module QueueAdapter
+ mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
+
+ def queue_adapter=(name_or_adapter)
+ @@queue_adapter = \
+ case name_or_adapter
+ when Symbol, String
+ load_adapter(name_or_adapter)
+ when Class
+ name_or_adapter
+ end
+ end
+
+ private
+ def load_adapter(name)
+ require "active_job/queue_adapters/#{name}_adapter"
+ "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
new file mode 100644
index 0000000000..7a6032e56b
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
@@ -0,0 +1,25 @@
+require 'backburner'
+
+module ActiveJob
+ module QueueAdapters
+ class BackburnerAdapter
+ class << self
+ def enqueue(job, *args)
+ Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ class << self
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
new file mode 100644
index 0000000000..bfeaa836d2
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -0,0 +1,23 @@
+require 'delayed_job'
+
+module ActiveJob
+ module QueueAdapters
+ class DelayedJobAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.new.delay(queue: job.queue_name).perform(job, *args)
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ JobWrapper.new.delay(queue: job.queue_name, run_at: Time.at(timestamp)).perform(job, *args)
+ end
+ end
+
+ class JobWrapper
+ def perform(job, *args)
+ job.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
new file mode 100644
index 0000000000..50d14a321d
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -0,0 +1,15 @@
+module ActiveJob
+ module QueueAdapters
+ class InlineAdapter
+ class << self
+ def enqueue(job, *args)
+ job.new.execute *args
+ end
+
+ def enqueue_at(*)
+ raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at https://github.com/rails/activejob")
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
new file mode 100644
index 0000000000..cdf4ae4ce9
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -0,0 +1,30 @@
+require 'qu'
+
+module ActiveJob
+ module QueueAdapters
+ class QuAdapter
+ class << self
+ def enqueue(job, *args)
+ Qu::Payload.new(klass: JobWrapper, args: [job.name, *args]).tap do |payload|
+ payload.instance_variable_set(:@queue, job.queue_name)
+ end.push
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper < Qu::Job
+ def initialize(job_name, *args)
+ @job = job_name.constantize
+ @args = args
+ end
+
+ def perform
+ @job.new.execute *@args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
new file mode 100644
index 0000000000..15a607bcb6
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -0,0 +1,23 @@
+require 'que'
+
+module ActiveJob
+ module QueueAdapters
+ class QueAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.enqueue job.name, *args, queue: job.queue_name
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper < Que::Job
+ def run(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
new file mode 100644
index 0000000000..c61e0e30db
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -0,0 +1,23 @@
+require 'queue_classic'
+
+module ActiveJob
+ module QueueAdapters
+ class QueueClassicAdapter
+ class << self
+ def enqueue(job, *args)
+ QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ def self.perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
new file mode 100644
index 0000000000..b228825f07
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
@@ -0,0 +1,38 @@
+require 'resque'
+require 'active_support/core_ext/enumerable'
+require 'active_support/core_ext/array/access'
+
+begin
+ require 'resque-scheduler'
+rescue LoadError
+ begin
+ require 'resque_scheduler'
+ rescue LoadError
+ $stderr.puts 'The ActiveJob resque adapter requires resque-scheduler. Please add it to your Gemfile and run bundle install'
+ raise e
+ end
+end
+
+module ActiveJob
+ module QueueAdapters
+ class ResqueAdapter
+ class << self
+ def enqueue(job, *args)
+ Resque.enqueue_to job.queue_name, JobWrapper, job.name, *args
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.name, *args
+ end
+ end
+
+ class JobWrapper
+ class << self
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
new file mode 100644
index 0000000000..f738a7d91c
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -0,0 +1,35 @@
+require 'sidekiq'
+
+module ActiveJob
+ module QueueAdapters
+ class SidekiqAdapter
+ class << self
+ def enqueue(job, *args)
+ #Sidekiq::Client does not support symbols as keys
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'queue' => job.queue_name,
+ 'args' => [ job, *args ],
+ 'retry' => true
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'queue' => job.queue_name,
+ 'args' => [ job, *args ],
+ 'retry' => true,
+ 'at' => timestamp
+ end
+ end
+
+ class JobWrapper
+ include Sidekiq::Worker
+
+ def perform(job_name, *args)
+ job_name.constantize.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
new file mode 100644
index 0000000000..051a8c3bd7
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -0,0 +1,34 @@
+require 'sneakers'
+require 'thread'
+
+module ActiveJob
+ module QueueAdapters
+ class SneakersAdapter
+ @monitor = Monitor.new
+
+ class << self
+ def enqueue(job, *args)
+ @monitor.synchronize do
+ JobWrapper.from_queue job.queue_name
+ JobWrapper.enqueue ActiveSupport::JSON.encode([ job.name, *args ])
+ end
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ include Sneakers::Worker
+ from_queue 'active_jobs_default'
+
+ def work(msg)
+ job_name, *args = ActiveSupport::JSON.decode(msg)
+ job_name.constantize.new.execute *args
+ ack!
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
new file mode 100644
index 0000000000..64b9c3ca15
--- /dev/null
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -0,0 +1,25 @@
+require 'sucker_punch'
+
+module ActiveJob
+ module QueueAdapters
+ class SuckerPunchAdapter
+ class << self
+ def enqueue(job, *args)
+ JobWrapper.new.async.perform job, *args
+ end
+
+ def enqueue_at(job, timestamp, *args)
+ raise NotImplementedError
+ end
+ end
+
+ class JobWrapper
+ include SuckerPunch::Job
+
+ def perform(job, *args)
+ job.new.execute *args
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
new file mode 100644
index 0000000000..859ddad034
--- /dev/null
+++ b/activejob/lib/active_job/queue_name.rb
@@ -0,0 +1,18 @@
+module ActiveJob
+ module QueueName
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ mattr_accessor(:queue_base_name) { "active_jobs" }
+
+ def queue_as(part_name)
+ self.queue_name = "#{queue_base_name}_#{part_name}"
+ end
+ end
+
+ included do
+ class_attribute :queue_name
+ self.queue_name = queue_base_name
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb
new file mode 100644
index 0000000000..08555d1d77
--- /dev/null
+++ b/activejob/lib/active_job/railtie.rb
@@ -0,0 +1,10 @@
+require 'active_model/railtie'
+
+module ActiveJob
+ # = Active Job Railtie
+ class Railtie < Rails::Railtie # :nodoc:
+ initializer 'active_job' do
+ ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger }
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/active_job/version.rb b/activejob/lib/active_job/version.rb
new file mode 100644
index 0000000000..7e646fa3c4
--- /dev/null
+++ b/activejob/lib/active_job/version.rb
@@ -0,0 +1,8 @@
+require_relative 'gem_version'
+
+module ActiveJob
+ # Returns the version of the currently loaded ActiveJob as a <tt>Gem::Version</tt>
+ def self.version
+ gem_version
+ end
+end
diff --git a/activejob/lib/activejob.rb b/activejob/lib/activejob.rb
new file mode 100644
index 0000000000..fea38731af
--- /dev/null
+++ b/activejob/lib/activejob.rb
@@ -0,0 +1 @@
+require 'active_job'
diff --git a/activejob/lib/rails/generators/active_job/job_generator.rb b/activejob/lib/rails/generators/active_job/job_generator.rb
new file mode 100644
index 0000000000..fe4af0d2cc
--- /dev/null
+++ b/activejob/lib/rails/generators/active_job/job_generator.rb
@@ -0,0 +1,22 @@
+require 'rails/generators/named_base'
+
+module ActiveJob
+ module Generators # :nodoc:
+ class JobGenerator < Rails::Generators::NamedBase # :nodoc:
+ desc 'This generator creates an active job file at app/jobs'
+
+ class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job'
+
+ def self.default_generator_root
+ File.dirname(__FILE__)
+ end
+
+ check_class_collision suffix: 'Job'
+
+ def create_job_file
+ template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/activejob/lib/rails/generators/active_job/templates/job.rb b/activejob/lib/rails/generators/active_job/templates/job.rb
new file mode 100644
index 0000000000..6a21616d30
--- /dev/null
+++ b/activejob/lib/rails/generators/active_job/templates/job.rb
@@ -0,0 +1,9 @@
+<% module_namespacing do -%>
+class <%= class_name %>Job < ActiveJob::Base
+ queue_as :<%= options[:queue] %>
+
+ def perform
+ # Do something later
+ end
+end
+<% end -%> \ No newline at end of file
diff --git a/activejob/test/adapters/backburner.rb b/activejob/test/adapters/backburner.rb
new file mode 100644
index 0000000000..65d05f850b
--- /dev/null
+++ b/activejob/test/adapters/backburner.rb
@@ -0,0 +1,3 @@
+require 'support/backburner/inline'
+
+ActiveJob::Base.queue_adapter = :backburner \ No newline at end of file
diff --git a/activejob/test/adapters/delayed_job.rb b/activejob/test/adapters/delayed_job.rb
new file mode 100644
index 0000000000..afd9c9deb7
--- /dev/null
+++ b/activejob/test/adapters/delayed_job.rb
@@ -0,0 +1,7 @@
+ActiveJob::Base.queue_adapter = :delayed_job
+
+$LOAD_PATH << File.dirname(__FILE__) + "/../support/delayed_job"
+
+Delayed::Worker.delay_jobs = false
+Delayed::Worker.backend = :test
+
diff --git a/activejob/test/adapters/inline.rb b/activejob/test/adapters/inline.rb
new file mode 100644
index 0000000000..e0092552c4
--- /dev/null
+++ b/activejob/test/adapters/inline.rb
@@ -0,0 +1 @@
+ActiveJob::Base.queue_adapter = :inline \ No newline at end of file
diff --git a/activejob/test/adapters/qu.rb b/activejob/test/adapters/qu.rb
new file mode 100644
index 0000000000..7728c843b4
--- /dev/null
+++ b/activejob/test/adapters/qu.rb
@@ -0,0 +1,3 @@
+require 'qu-immediate'
+
+ActiveJob::Base.queue_adapter = :qu
diff --git a/activejob/test/adapters/que.rb b/activejob/test/adapters/que.rb
new file mode 100644
index 0000000000..640061bf54
--- /dev/null
+++ b/activejob/test/adapters/que.rb
@@ -0,0 +1,2 @@
+ActiveJob::Base.queue_adapter = :que
+Que.mode = :sync
diff --git a/activejob/test/adapters/queue_classic.rb b/activejob/test/adapters/queue_classic.rb
new file mode 100644
index 0000000000..ad5ced3cc2
--- /dev/null
+++ b/activejob/test/adapters/queue_classic.rb
@@ -0,0 +1,2 @@
+require 'support/queue_classic/inline'
+ActiveJob::Base.queue_adapter = :queue_classic
diff --git a/activejob/test/adapters/resque.rb b/activejob/test/adapters/resque.rb
new file mode 100644
index 0000000000..af7080358d
--- /dev/null
+++ b/activejob/test/adapters/resque.rb
@@ -0,0 +1,2 @@
+ActiveJob::Base.queue_adapter = :resque
+Resque.inline = true
diff --git a/activejob/test/adapters/sidekiq.rb b/activejob/test/adapters/sidekiq.rb
new file mode 100644
index 0000000000..cd9d2034de
--- /dev/null
+++ b/activejob/test/adapters/sidekiq.rb
@@ -0,0 +1,2 @@
+require 'sidekiq/testing/inline'
+ActiveJob::Base.queue_adapter = :sidekiq
diff --git a/activejob/test/adapters/sneakers.rb b/activejob/test/adapters/sneakers.rb
new file mode 100644
index 0000000000..204166a700
--- /dev/null
+++ b/activejob/test/adapters/sneakers.rb
@@ -0,0 +1,2 @@
+require 'support/sneakers/inline'
+ActiveJob::Base.queue_adapter = :sneakers
diff --git a/activejob/test/adapters/sucker_punch.rb b/activejob/test/adapters/sucker_punch.rb
new file mode 100644
index 0000000000..d2d1712946
--- /dev/null
+++ b/activejob/test/adapters/sucker_punch.rb
@@ -0,0 +1,2 @@
+require 'sucker_punch/testing/inline'
+ActiveJob::Base.queue_adapter = :sucker_punch
diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb
new file mode 100644
index 0000000000..7f6f4c1159
--- /dev/null
+++ b/activejob/test/cases/adapter_test.rb
@@ -0,0 +1,56 @@
+require 'helper'
+
+class AdapterTest < ActiveSupport::TestCase
+ setup { @old_adapter = ActiveJob::Base.queue_adapter }
+ teardown { ActiveJob::Base.queue_adapter = @old_adapter }
+
+ test 'should load inline adapter' do
+ ActiveJob::Base.queue_adapter = :inline
+ assert_equal ActiveJob::QueueAdapters::InlineAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Delayed Job adapter' do
+ ActiveJob::Base.queue_adapter = :delayed_job
+ assert_equal ActiveJob::QueueAdapters::DelayedJobAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Qu adapter' do
+ ActiveJob::Base.queue_adapter = :qu
+ assert_equal ActiveJob::QueueAdapters::QuAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Que adapter' do
+ ActiveJob::Base.queue_adapter = :que
+ assert_equal ActiveJob::QueueAdapters::QueAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Queue Classic adapter' do
+ ActiveJob::Base.queue_adapter = :queue_classic
+ assert_equal ActiveJob::QueueAdapters::QueueClassicAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Resque adapter' do
+ ActiveJob::Base.queue_adapter = :resque
+ assert_equal ActiveJob::QueueAdapters::ResqueAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Sidekiq adapter' do
+ ActiveJob::Base.queue_adapter = :sidekiq
+ assert_equal ActiveJob::QueueAdapters::SidekiqAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Sucker Punch adapter' do
+ ActiveJob::Base.queue_adapter = :sucker_punch
+ assert_equal ActiveJob::QueueAdapters::SuckerPunchAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Sneakers adapter' do
+ ActiveJob::Base.queue_adapter = :sneakers
+ assert_equal ActiveJob::QueueAdapters::SneakersAdapter, ActiveJob::Base.queue_adapter
+ end
+
+ test 'should load Backburner adapter' do
+ ActiveJob::Base.queue_adapter = :backburner
+ assert_equal ActiveJob::QueueAdapters::BackburnerAdapter, ActiveJob::Base.queue_adapter
+ end
+end
diff --git a/activejob/test/cases/callbacks_test.rb b/activejob/test/cases/callbacks_test.rb
new file mode 100644
index 0000000000..9a0657ee89
--- /dev/null
+++ b/activejob/test/cases/callbacks_test.rb
@@ -0,0 +1,22 @@
+require 'helper'
+require 'jobs/callback_job'
+
+require 'active_support/core_ext/object/inclusion'
+
+class CallbacksTest < ActiveSupport::TestCase
+ test 'perform callbacks' do
+ performed_callback_job = CallbackJob.new.tap { |j| j.execute("A-JOB-ID") }
+ assert "CallbackJob ran before_perform".in? performed_callback_job.history
+ assert "CallbackJob ran after_perform".in? performed_callback_job.history
+ assert "CallbackJob ran around_perform_start".in? performed_callback_job.history
+ assert "CallbackJob ran around_perform_stop".in? performed_callback_job.history
+ end
+
+ test 'enqueue callbacks' do
+ enqueued_callback_job = CallbackJob.enqueue
+ assert "CallbackJob ran before_enqueue".in? enqueued_callback_job.history
+ assert "CallbackJob ran after_enqueue".in? enqueued_callback_job.history
+ assert "CallbackJob ran around_enqueue_start".in? enqueued_callback_job.history
+ assert "CallbackJob ran around_enqueue_stop".in? enqueued_callback_job.history
+ end
+end
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
new file mode 100644
index 0000000000..b1e24db22e
--- /dev/null
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -0,0 +1,15 @@
+require 'helper'
+require 'jobs/gid_job'
+require 'models/person'
+
+class JobSerializationTest < ActiveSupport::TestCase
+ setup do
+ $BUFFER = []
+ @person = Person.find(5)
+ end
+
+ test 'serialize job with gid' do
+ GidJob.enqueue @person
+ assert_equal "Person with ID: 5", $BUFFER.pop
+ end
+end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
new file mode 100644
index 0000000000..537702edd4
--- /dev/null
+++ b/activejob/test/cases/logging_test.rb
@@ -0,0 +1,94 @@
+require 'helper'
+require "active_support/log_subscriber/test_helper"
+require 'jobs/logging_job'
+require 'jobs/nested_job'
+
+class AdapterTest < ActiveSupport::TestCase
+ include ActiveSupport::LogSubscriber::TestHelper
+ include ActiveSupport::Logger::Severity
+
+ class TestLogger < ActiveSupport::Logger
+ def initialize
+ @file = StringIO.new
+ super(@file)
+ end
+
+ def messages
+ @file.rewind
+ @file.read
+ end
+ end
+
+ def setup
+ super
+ $BUFFER = []
+ @old_logger = ActiveJob::Base.logger
+ @logger = ActiveSupport::TaggedLogging.new(TestLogger.new)
+ set_logger @logger
+ ActiveJob::Logging::LogSubscriber.attach_to :active_job
+ end
+
+ def teardown
+ super
+ ActiveJob::Logging::LogSubscriber.log_subscribers.pop
+ ActiveJob::Base.logger = @old_logger
+ end
+
+ def set_logger(logger)
+ ActiveJob::Base.logger = logger
+ end
+
+
+ def test_uses_active_job_as_tag
+ HelloJob.enqueue "Cristian"
+ assert_match(/\[ActiveJob\]/, @logger.messages)
+ end
+
+ def test_enqueue_job_logging
+ HelloJob.enqueue "Cristian"
+ assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
+ end
+
+ def test_perform_job_logging
+ LoggingJob.enqueue "Dummy"
+ assert_match(/Performing LoggingJob from .*? with arguments:.*Dummy/, @logger.messages)
+ assert_match(/Dummy, here is it: Dummy/, @logger.messages)
+ assert_match(/Performed LoggingJob from .*? in .*ms/, @logger.messages)
+ end
+
+ def test_perform_uses_job_name_job_logging
+ LoggingJob.enqueue "Dummy"
+ assert_match(/\[LoggingJob\]/, @logger.messages)
+ end
+
+ def test_perform_uses_job_id_job_logging
+ LoggingJob.enqueue "Dummy"
+ assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages)
+ end
+
+ def test_perform_nested_jobs_logging
+ NestedJob.enqueue
+ assert_match(/\[LoggingJob\] \[.*?\]/, @logger.messages)
+ assert_match(/\[ActiveJob\] Enqueued NestedJob \(Job ID: .*\) to/, @logger.messages)
+ assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performing NestedJob from/, @logger.messages)
+ assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Enqueued LoggingJob \(Job ID: .*?\) to .* with arguments: "NestedJob"/, @logger.messages)
+ assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performing LoggingJob from .* with arguments: "NestedJob"/, @logger.messages)
+ assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Dummy, here is it: NestedJob/, @logger.messages)
+ assert_match(/\[ActiveJob\].*\[LoggingJob\] \[LOGGING-JOB-ID\] Performed LoggingJob from .* in/, @logger.messages)
+ assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performed NestedJob from .* in/, @logger.messages)
+ end
+
+ def test_enqueue_at_job_logging
+ HelloJob.enqueue_at 1, "Cristian"
+ assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
+ rescue NotImplementedError
+ skip
+ end
+
+ def test_enqueue_in_job_logging
+ HelloJob.enqueue_in 2, "Cristian"
+ assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
+ rescue NotImplementedError
+ skip
+ end
+end
diff --git a/activejob/test/cases/parameters_test.rb b/activejob/test/cases/parameters_test.rb
new file mode 100644
index 0000000000..76e8a8059a
--- /dev/null
+++ b/activejob/test/cases/parameters_test.rb
@@ -0,0 +1,77 @@
+require 'helper'
+require 'active_job/arguments'
+require 'models/person'
+require 'active_support/core_ext/hash/indifferent_access'
+
+class ParameterSerializationTest < ActiveSupport::TestCase
+ test 'should make no change to regular values' do
+ assert_equal [ 1, "something" ], ActiveJob::Arguments.serialize([ 1, "something" ])
+ end
+
+ test 'should not allow complex objects' do
+ assert_equal [ nil ], ActiveJob::Arguments.serialize([ nil ])
+ assert_equal [ 1 ], ActiveJob::Arguments.serialize([ 1 ])
+ assert_equal [ 1.0 ], ActiveJob::Arguments.serialize([ 1.0 ])
+ assert_equal [ 'a' ], ActiveJob::Arguments.serialize([ 'a' ])
+ assert_equal [ true ], ActiveJob::Arguments.serialize([ true ])
+ assert_equal [ false ], ActiveJob::Arguments.serialize([ false ])
+ assert_equal [ { "a" => 1, "b" => 2 } ], ActiveJob::Arguments.serialize([ { a: 1, "b" => 2 } ])
+ assert_equal [ [ 1 ] ], ActiveJob::Arguments.serialize([ [ 1 ] ])
+ assert_equal [ 1_000_000_000_000_000_000_000 ], ActiveJob::Arguments.serialize([ 1_000_000_000_000_000_000_000 ])
+
+ err = assert_raises RuntimeError do
+ ActiveJob::Arguments.serialize([ 1, self ])
+ end
+ assert_equal "Unsupported argument type: #{self.class.name}", err.message
+ end
+
+ test 'should dive deep into arrays or hashes' do
+ assert_equal [ { "a" => Person.find(5).gid.to_s }.with_indifferent_access ], ActiveJob::Arguments.serialize([ { a: Person.find(5) } ])
+ assert_equal [ [ Person.find(5).gid.to_s ] ], ActiveJob::Arguments.serialize([ [ Person.find(5) ] ])
+ end
+
+ test 'should dive deep into arrays or hashes and raise exception on complex objects' do
+ err = assert_raises RuntimeError do
+ ActiveJob::Arguments.serialize([ 1, [self] ])
+ end
+ assert_equal "Unsupported argument type: #{self.class.name}", err.message
+ end
+
+ test 'shoud dive deep into hashes and allow raise exception on not string/symbol keys' do
+ err = assert_raises RuntimeError do
+ ActiveJob::Arguments.serialize([ [ { 1 => 2 } ] ])
+ end
+ assert_equal "Unsupported hash key type: Fixnum", err.message
+ end
+
+ test 'should serialize records with global id' do
+ assert_equal [ Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ Person.find(5) ])
+ end
+
+ test 'should serialize values and records together' do
+ assert_equal [ 3, Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ 3, Person.find(5) ])
+ end
+end
+
+class ParameterDeserializationTest < ActiveSupport::TestCase
+ test 'should make no change to regular values' do
+ assert_equal [ 1, "something" ], ActiveJob::Arguments.deserialize([ 1, "something" ])
+ end
+
+ test 'should deserialize records with global id' do
+ assert_equal [ Person.find(5) ], ActiveJob::Arguments.deserialize([ Person.find(5).gid ])
+ end
+
+ test 'should serialize values and records together' do
+ assert_equal [ 3, Person.find(5) ], ActiveJob::Arguments.deserialize([ 3, Person.find(5).gid ])
+ end
+
+ test 'should dive deep when deserialising arrays' do
+ assert_equal [ [ 3, Person.find(5) ] ], ActiveJob::Arguments.deserialize([ [ 3, Person.find(5).gid ] ])
+ end
+
+ test 'should dive deep when deserialising hashes' do
+ assert_equal [ { "5" => Person.find(5) } ], ActiveJob::Arguments.deserialize([ { "5" => Person.find(5).gid } ])
+ end
+
+end
diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb
new file mode 100644
index 0000000000..852643b9f6
--- /dev/null
+++ b/activejob/test/cases/queue_naming_test.rb
@@ -0,0 +1,21 @@
+require 'helper'
+require 'jobs/hello_job'
+
+class QueueNamingTest < ActiveSupport::TestCase
+ test 'name derived from base' do
+ assert_equal "active_jobs", HelloJob.queue_name
+ end
+
+ test 'name appended in job' do
+ begin
+ HelloJob.queue_as :greetings
+ LoggingJob.queue_as :bookkeeping
+
+ assert_equal "active_jobs", NestedJob.queue_name
+ assert_equal "active_jobs_greetings", HelloJob.queue_name
+ assert_equal "active_jobs_bookkeeping", LoggingJob.queue_name
+ ensure
+ HelloJob.queue_name = LoggingJob.queue_name = ActiveJob::Base.queue_base_name
+ end
+ end
+end
diff --git a/activejob/test/cases/queuing_test.rb b/activejob/test/cases/queuing_test.rb
new file mode 100644
index 0000000000..3dd9ecd8d2
--- /dev/null
+++ b/activejob/test/cases/queuing_test.rb
@@ -0,0 +1,44 @@
+require 'helper'
+require 'jobs/hello_job'
+require 'active_support/core_ext/numeric/time'
+
+
+class QueuingTest < ActiveSupport::TestCase
+ setup do
+ $BUFFER = []
+ end
+
+ test 'run queued job' do
+ HelloJob.enqueue
+ assert_equal "David says hello", $BUFFER.pop
+ end
+
+ test 'run queued job with arguments' do
+ HelloJob.enqueue "Jamie"
+ assert_equal "Jamie says hello", $BUFFER.pop
+ end
+
+ test 'run queued job later' do
+ begin
+ result = HelloJob.enqueue_at 1.second.ago, "Jamie"
+ assert result
+ rescue NotImplementedError
+ skip
+ end
+ end
+
+ test 'job returned by enqueue has the arguments available' do
+ job = HelloJob.enqueue "Jamie"
+ assert_equal [ "Jamie" ], job.arguments
+ end
+
+
+ test 'job returned by enqueue_at has the timestamp available' do
+ begin
+ job = HelloJob.enqueue_at Time.utc(2014, 1, 1)
+ assert_equal Time.utc(2014, 1, 1), job.enqueued_at
+ rescue NotImplementedError
+ skip
+ end
+ end
+end
diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb
new file mode 100644
index 0000000000..3d4831bc62
--- /dev/null
+++ b/activejob/test/cases/rescue_test.rb
@@ -0,0 +1,23 @@
+require 'helper'
+require 'jobs/rescue_job'
+
+require 'active_support/core_ext/object/inclusion'
+
+class RescueTest < ActiveSupport::TestCase
+ setup do
+ $BUFFER = []
+ end
+
+ test 'rescue perform exception with retry' do
+ job = RescueJob.new
+ job.execute(SecureRandom.uuid, "david")
+ assert_equal [ "rescued from ArgumentError", "performed beautifully" ], $BUFFER
+ end
+
+ test 'let through unhandled perform exception' do
+ job = RescueJob.new
+ assert_raises(RescueJob::OtherError) do
+ job.execute(SecureRandom.uuid, "other")
+ end
+ end
+end
diff --git a/activejob/test/dummy/Rakefile b/activejob/test/dummy/Rakefile
new file mode 100644
index 0000000000..9866295e6a
--- /dev/null
+++ b/activejob/test/dummy/Rakefile
@@ -0,0 +1,3 @@
+require 'sneakers/tasks'
+require File.expand_path('../config/application', __FILE__)
+Rails.application.load_tasks
diff --git a/activejob/test/dummy/app/assets/images/.keep b/activejob/test/dummy/app/assets/images/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/app/assets/images/.keep
diff --git a/activejob/test/dummy/app/controllers/application_controller.rb b/activejob/test/dummy/app/controllers/application_controller.rb
new file mode 100644
index 0000000000..1c07694e9d
--- /dev/null
+++ b/activejob/test/dummy/app/controllers/application_controller.rb
@@ -0,0 +1,3 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery with: :exception
+end
diff --git a/activejob/test/dummy/app/controllers/concerns/.keep b/activejob/test/dummy/app/controllers/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/app/controllers/concerns/.keep
diff --git a/activejob/test/dummy/app/helpers/application_helper.rb b/activejob/test/dummy/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..de6be7945c
--- /dev/null
+++ b/activejob/test/dummy/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
diff --git a/activejob/test/dummy/app/jobs/test_job.rb b/activejob/test/dummy/app/jobs/test_job.rb
new file mode 100644
index 0000000000..281771a851
--- /dev/null
+++ b/activejob/test/dummy/app/jobs/test_job.rb
@@ -0,0 +1,9 @@
+class TestJob < ActiveJob::Base
+ queue_as :default
+
+ def perform(x)
+ File.open(Rails.root.join("tmp/#{x}"), "w+") do |f|
+ f.write x
+ end
+ end
+end
diff --git a/activejob/test/dummy/app/mailers/.keep b/activejob/test/dummy/app/mailers/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/app/mailers/.keep
diff --git a/activejob/test/dummy/app/models/.keep b/activejob/test/dummy/app/models/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/app/models/.keep
diff --git a/activejob/test/dummy/app/models/concerns/.keep b/activejob/test/dummy/app/models/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/app/models/concerns/.keep
diff --git a/activejob/test/dummy/config.ru b/activejob/test/dummy/config.ru
new file mode 100644
index 0000000000..5bc2a619e8
--- /dev/null
+++ b/activejob/test/dummy/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Rails.application
diff --git a/activejob/test/dummy/config/application.rb b/activejob/test/dummy/config/application.rb
new file mode 100644
index 0000000000..8b06039a68
--- /dev/null
+++ b/activejob/test/dummy/config/application.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../boot', __FILE__)
+require 'rails/all'
+Bundler.require(*Rails.groups)
+
+module Dummy
+ class Application < Rails::Application
+ end
+end
+
diff --git a/activejob/test/dummy/config/boot.rb b/activejob/test/dummy/config/boot.rb
new file mode 100644
index 0000000000..6266cfc509
--- /dev/null
+++ b/activejob/test/dummy/config/boot.rb
@@ -0,0 +1,5 @@
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
diff --git a/activejob/test/dummy/config/database.yml b/activejob/test/dummy/config/database.yml
new file mode 100644
index 0000000000..f5f3aaf1fa
--- /dev/null
+++ b/activejob/test/dummy/config/database.yml
@@ -0,0 +1,3 @@
+test:
+ adapter: sqlite3
+ database: "db/test.sqlite3"
diff --git a/activejob/test/dummy/config/environment.rb b/activejob/test/dummy/config/environment.rb
new file mode 100644
index 0000000000..7fd2f91b8f
--- /dev/null
+++ b/activejob/test/dummy/config/environment.rb
@@ -0,0 +1,2 @@
+require File.expand_path('../application', __FILE__)
+Rails.application.initialize!
diff --git a/activejob/test/dummy/config/environments/test.rb b/activejob/test/dummy/config/environments/test.rb
new file mode 100644
index 0000000000..ff0318412f
--- /dev/null
+++ b/activejob/test/dummy/config/environments/test.rb
@@ -0,0 +1,13 @@
+Rails.application.configure do
+ config.cache_classes = true
+ config.eager_load = false
+ config.serve_static_assets = true
+ config.static_cache_control = 'public, max-age=3600'
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+ config.action_dispatch.show_exceptions = false
+ config.action_controller.allow_forgery_protection = false
+ config.action_mailer.delivery_method = :test
+ config.active_support.deprecation = :stderr
+ config.log_level = :debug
+end
diff --git a/activejob/test/dummy/config/initializers/activejob.rb b/activejob/test/dummy/config/initializers/activejob.rb
new file mode 100644
index 0000000000..5fcde86c96
--- /dev/null
+++ b/activejob/test/dummy/config/initializers/activejob.rb
@@ -0,0 +1,65 @@
+case ENV['AJADAPTER']
+when "delayed_job"
+ ActiveJob::Base.queue_adapter = :delayed_job
+when "sidekiq"
+ ActiveJob::Base.queue_adapter = :sidekiq
+when "resque"
+ ActiveJob::Base.queue_adapter = :resque
+ Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "tcp://127.0.0.1:6379/12", :thread_safe => true)
+ Resque.logger = Rails.logger
+when 'qu'
+ ActiveJob::Base.queue_adapter = :qu
+ ENV['REDISTOGO_URL'] = "tcp://127.0.0.1:6379/12"
+ backend = Qu::Backend::Redis.new
+ backend.namespace = "active_jobs_int_test"
+ Qu.backend = backend
+ Qu.logger = Rails.logger
+ Qu.interval = 0.5
+when 'que'
+ ActiveJob::Base.queue_adapter = :que
+ QUE_URL = ENV['QUE_DATABASE_URL'] || 'postgres://localhost/active_jobs_que_int_test'
+ uri = URI.parse(QUE_URL)
+ user = uri.user||ENV['USER']
+ pass = uri.password
+ db = uri.path[1..-1]
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ Que.connection = Sequel.connect(QUE_URL)
+ Que.migrate!
+ Que.mode = :off
+ Que.worker_count = 1
+when 'queue_classic'
+ ENV['QC_DATABASE_URL'] ||= 'postgres://localhost/active_jobs_qc_int_test'
+ ENV['QC_LISTEN_TIME'] = "0.5"
+ ActiveJob::Base.queue_adapter = :queue_classic
+ uri = URI.parse(ENV['QC_DATABASE_URL'])
+ user = uri.user||ENV['USER']
+ pass = uri.password
+ db = uri.path[1..-1]
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database "#{db}"' -U #{user} -t template1}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ QC::Setup.create
+when 'sidekiq'
+ ActiveJob::Base.queue_adapter = :sidekiq
+when 'sneakers'
+ ActiveJob::Base.queue_adapter = :sneakers
+ Sneakers.configure :heartbeat => 2,
+ :amqp => 'amqp://guest:guest@localhost:5672',
+ :vhost => '/',
+ :exchange => 'active_jobs_sneakers_int_test',
+ :exchange_type => :direct,
+ :daemonize => true,
+ :threads => 1,
+ :workers => 1,
+ :pid_path => Rails.root.join("tmp/sneakers.pid").to_s,
+ :log => Rails.root.join("log/sneakers.log").to_s
+when 'sucker_punch'
+ ActiveJob::Base.queue_adapter = :sucker_punch
+when 'backburner'
+ ActiveJob::Base.queue_adapter = :backburner
+ Backburner.configure do |config|
+ config.logger = Rails.logger
+ end
+else
+ ActiveJob::Base.queue_adapter = nil
+end
diff --git a/activejob/test/dummy/config/initializers/session_store.rb b/activejob/test/dummy/config/initializers/session_store.rb
new file mode 100644
index 0000000000..70a5a506a9
--- /dev/null
+++ b/activejob/test/dummy/config/initializers/session_store.rb
@@ -0,0 +1 @@
+Rails.application.config.session_store :cookie_store, key: '_dummy_session'
diff --git a/activejob/test/dummy/config/routes.rb b/activejob/test/dummy/config/routes.rb
new file mode 100644
index 0000000000..1daf9a4121
--- /dev/null
+++ b/activejob/test/dummy/config/routes.rb
@@ -0,0 +1,2 @@
+Rails.application.routes.draw do
+end
diff --git a/activejob/test/dummy/config/secrets.yml b/activejob/test/dummy/config/secrets.yml
new file mode 100644
index 0000000000..7dfacb38ea
--- /dev/null
+++ b/activejob/test/dummy/config/secrets.yml
@@ -0,0 +1,2 @@
+test:
+ secret_key_base: b83ee5aeada663bc4270a1817d0ca43b2784017cc77dc8afcd60967cc968d4ce30caff9eb682766129e18a4048c4d5ebf14eabf463fc37ad67c18934f4345545
diff --git a/activejob/test/dummy/db/migrate/20140804200445_create_delayed_jobs.rb b/activejob/test/dummy/db/migrate/20140804200445_create_delayed_jobs.rb
new file mode 100644
index 0000000000..ec0dd93ce1
--- /dev/null
+++ b/activejob/test/dummy/db/migrate/20140804200445_create_delayed_jobs.rb
@@ -0,0 +1,22 @@
+class CreateDelayedJobs < ActiveRecord::Migration
+ def self.up
+ create_table :delayed_jobs, :force => true do |table|
+ table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
+ table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
+ table.text :handler, :null => false # YAML-encoded string of the object that will do work
+ table.text :last_error # reason for last failure (See Note below)
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
+ table.datetime :locked_at # Set when a client is working on this object
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
+ table.string :locked_by # Who is working on this object (if locked)
+ table.string :queue # The name of the queue this job is in
+ table.timestamps
+ end
+
+ add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
+ end
+
+ def self.down
+ drop_table :delayed_jobs
+ end
+end
diff --git a/activejob/test/dummy/db/schema.rb b/activejob/test/dummy/db/schema.rb
new file mode 100644
index 0000000000..012a099f9c
--- /dev/null
+++ b/activejob/test/dummy/db/schema.rb
@@ -0,0 +1,32 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20140804200445) do
+
+ create_table "delayed_jobs", force: true do |t|
+ t.integer "priority", default: 0, null: false
+ t.integer "attempts", default: 0, null: false
+ t.text "handler", null: false
+ t.text "last_error"
+ t.datetime "run_at"
+ t.datetime "locked_at"
+ t.datetime "failed_at"
+ t.string "locked_by"
+ t.string "queue"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority"
+
+end
diff --git a/activejob/test/dummy/db/test.sqlite3 b/activejob/test/dummy/db/test.sqlite3
new file mode 100644
index 0000000000..671e20102e
--- /dev/null
+++ b/activejob/test/dummy/db/test.sqlite3
Binary files differ
diff --git a/activejob/test/dummy/lib/assets/.keep b/activejob/test/dummy/lib/assets/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/lib/assets/.keep
diff --git a/activejob/test/dummy/log/.keep b/activejob/test/dummy/log/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/log/.keep
diff --git a/activejob/test/dummy/tmp/.keep b/activejob/test/dummy/tmp/.keep
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/dummy/tmp/.keep
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
new file mode 100644
index 0000000000..104dabd861
--- /dev/null
+++ b/activejob/test/helper.rb
@@ -0,0 +1,19 @@
+require 'bundler'
+Bundler.setup
+
+$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
+
+require 'active_job'
+
+adapter = ENV['AJADAPTER'] || 'inline'
+puts "Testing#{" integration" if ENV['AJ_INTEGRATION_TESTS']} using #{adapter}"
+
+if ENV['AJ_INTEGRATION_TESTS']
+ require 'support/integration/helper'
+else
+ require "adapters/#{adapter}"
+end
+
+require 'active_support/testing/autorun'
+
+ActiveJob::Base.logger.level = Logger::DEBUG
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
new file mode 100644
index 0000000000..bf6137af4c
--- /dev/null
+++ b/activejob/test/integration/queuing_test.rb
@@ -0,0 +1,18 @@
+require 'helper'
+require 'jobs/logging_job'
+require 'active_support/core_ext/numeric/time'
+
+
+class QueuingTest < ActiveSupport::TestCase
+ setup do
+
+ end
+
+ test 'run queued job' do
+ id = "AJ-#{SecureRandom.uuid}"
+ TestJob.enqueue id
+ sleep 2
+ assert Dummy::Application.root.join("tmp/#{id}").exist?
+ end
+
+end
diff --git a/activejob/test/jobs/callback_job.rb b/activejob/test/jobs/callback_job.rb
new file mode 100644
index 0000000000..056dd073e8
--- /dev/null
+++ b/activejob/test/jobs/callback_job.rb
@@ -0,0 +1,32 @@
+class CallbackJob < ActiveJob::Base
+ before_perform ->(job) { job.history << "CallbackJob ran before_perform" }
+ after_perform ->(job) { job.history << "CallbackJob ran after_perform" }
+
+ before_enqueue ->(job) { job.history << "CallbackJob ran before_enqueue" }
+ after_enqueue ->(job) { job.history << "CallbackJob ran after_enqueue" }
+
+ around_perform :around_perform
+ around_enqueue :around_enqueue
+
+
+ def perform(person = "david")
+ # NOTHING!
+ end
+
+ def history
+ @history ||= []
+ end
+
+ # FIXME: Not sure why these can't be declared inline like before/after
+ def around_perform
+ history << "CallbackJob ran around_perform_start"
+ yield
+ history << "CallbackJob ran around_perform_stop"
+ end
+
+ def around_enqueue
+ history << "CallbackJob ran around_enqueue_start"
+ yield
+ history << "CallbackJob ran around_enqueue_stop"
+ end
+end
diff --git a/activejob/test/jobs/gid_job.rb b/activejob/test/jobs/gid_job.rb
new file mode 100644
index 0000000000..c69e38d3cc
--- /dev/null
+++ b/activejob/test/jobs/gid_job.rb
@@ -0,0 +1,6 @@
+class GidJob < ActiveJob::Base
+ def perform(person)
+ $BUFFER << "Person with ID: #{person.id}"
+ end
+end
+ \ No newline at end of file
diff --git a/activejob/test/jobs/hello_job.rb b/activejob/test/jobs/hello_job.rb
new file mode 100644
index 0000000000..25441dd0c8
--- /dev/null
+++ b/activejob/test/jobs/hello_job.rb
@@ -0,0 +1,5 @@
+class HelloJob < ActiveJob::Base
+ def perform(greeter = "David")
+ $BUFFER << "#{greeter} says hello"
+ end
+end
diff --git a/activejob/test/jobs/logging_job.rb b/activejob/test/jobs/logging_job.rb
new file mode 100644
index 0000000000..d84ed8589b
--- /dev/null
+++ b/activejob/test/jobs/logging_job.rb
@@ -0,0 +1,10 @@
+class LoggingJob < ActiveJob::Base
+ def perform(dummy)
+ logger.info "Dummy, here is it: #{dummy}"
+ end
+
+ def job_id
+ "LOGGING-JOB-ID"
+ end
+end
+
diff --git a/activejob/test/jobs/nested_job.rb b/activejob/test/jobs/nested_job.rb
new file mode 100644
index 0000000000..fd66f68991
--- /dev/null
+++ b/activejob/test/jobs/nested_job.rb
@@ -0,0 +1,10 @@
+class NestedJob < ActiveJob::Base
+ def perform
+ LoggingJob.enqueue "NestedJob"
+ end
+
+ def job_id
+ "NESTED-JOB-ID"
+ end
+end
+
diff --git a/activejob/test/jobs/rescue_job.rb b/activejob/test/jobs/rescue_job.rb
new file mode 100644
index 0000000000..acf2b81515
--- /dev/null
+++ b/activejob/test/jobs/rescue_job.rb
@@ -0,0 +1,20 @@
+class RescueJob < ActiveJob::Base
+ class OtherError < StandardError; end
+
+ rescue_from(ArgumentError) do
+ $BUFFER << "rescued from ArgumentError"
+ arguments[0] = "DIFFERENT!"
+ retry_now
+ end
+
+ def perform(person = "david")
+ case person
+ when "david"
+ raise ArgumentError, "Hair too good"
+ when "other"
+ raise OtherError
+ else
+ $BUFFER << "performed beautifully"
+ end
+ end
+end
diff --git a/activejob/test/models/person.rb b/activejob/test/models/person.rb
new file mode 100644
index 0000000000..a5bdbc462b
--- /dev/null
+++ b/activejob/test/models/person.rb
@@ -0,0 +1,19 @@
+require 'active_model/global_identification'
+
+class Person
+ include ActiveModel::GlobalIdentification
+
+ attr_reader :id
+
+ def self.find(id)
+ new(id)
+ end
+
+ def initialize(id)
+ @id = id
+ end
+
+ def ==(other_person)
+ other_person.is_a?(Person) && id.to_s == other_person.id.to_s
+ end
+end
diff --git a/activejob/test/support/backburner/inline.rb b/activejob/test/support/backburner/inline.rb
new file mode 100644
index 0000000000..f761b53e27
--- /dev/null
+++ b/activejob/test/support/backburner/inline.rb
@@ -0,0 +1,8 @@
+require 'backburner'
+
+Backburner::Worker.class_eval do
+ class << self; alias_method :original_enqueue, :enqueue; end
+ def self.enqueue(job_class, args=[], opts={})
+ job_class.perform(*args)
+ end
+end \ No newline at end of file
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
new file mode 100644
index 0000000000..b50ed36fc2
--- /dev/null
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -0,0 +1,113 @@
+#copied from https://github.com/collectiveidea/delayed_job/blob/master/spec/delayed/backend/test.rb
+require 'ostruct'
+
+# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
+module Delayed
+ module Backend
+ module Test
+ class Job
+ attr_accessor :id
+ attr_accessor :priority
+ attr_accessor :attempts
+ attr_accessor :handler
+ attr_accessor :last_error
+ attr_accessor :run_at
+ attr_accessor :locked_at
+ attr_accessor :locked_by
+ attr_accessor :failed_at
+ attr_accessor :queue
+
+ include Delayed::Backend::Base
+
+ cattr_accessor :id
+ self.id = 0
+
+ def initialize(hash = {})
+ self.attempts = 0
+ self.priority = 0
+ self.id = (self.class.id += 1)
+ hash.each{|k,v| send(:"#{k}=", v)}
+ end
+
+ @jobs = []
+ def self.all
+ @jobs
+ end
+
+ def self.count
+ all.size
+ end
+
+ def self.delete_all
+ all.clear
+ end
+
+ def self.create(attrs = {})
+ new(attrs).tap do |o|
+ o.save
+ end
+ end
+
+ def self.create!(*args); create(*args); end
+
+ def self.clear_locks!(worker_name)
+ all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
+ end
+
+ # Find a few candidate jobs to run (in case some immediately get locked by others).
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
+ jobs = all.select do |j|
+ j.run_at <= db_time_now &&
+ (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
+ !j.failed?
+ end
+
+ jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
+ jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
+ jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
+ jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
+ end
+
+ # Lock this job for this worker.
+ # Returns true if we have the lock, false otherwise.
+ def lock_exclusively!(max_run_time, worker)
+ now = self.class.db_time_now
+ if locked_by != worker
+ # We don't own this job so we will update the locked_by name and the locked_at
+ self.locked_at = now
+ self.locked_by = worker
+ end
+
+ return true
+ end
+
+ def self.db_time_now
+ Time.current
+ end
+
+ def update_attributes(attrs = {})
+ attrs.each{|k,v| send(:"#{k}=", v)}
+ save
+ end
+
+ def destroy
+ self.class.all.delete(self)
+ end
+
+ def save
+ self.run_at ||= Time.current
+
+ self.class.all << self unless self.class.all.include?(self)
+ true
+ end
+
+ def save!; save; end
+
+ def reload
+ reset
+ self
+ end
+ end
+ end
+ end
+end
diff --git a/activejob/test/support/delayed_job/delayed/serialization/test.rb b/activejob/test/support/delayed_job/delayed/serialization/test.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/activejob/test/support/delayed_job/delayed/serialization/test.rb
diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb
new file mode 100644
index 0000000000..7271d50a45
--- /dev/null
+++ b/activejob/test/support/integration/adapters/backburner.rb
@@ -0,0 +1,15 @@
+module BackburnerJobsManager
+ def clear_jobs
+ Backburner::Worker.connection.tubes.all.map &:clear
+ end
+
+ def start_workers
+ @thread = Thread.new { Backburner.work "active-jobs-default" }
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+
+end
+
diff --git a/activejob/test/support/integration/adapters/delayed_job.rb b/activejob/test/support/integration/adapters/delayed_job.rb
new file mode 100644
index 0000000000..3e55933438
--- /dev/null
+++ b/activejob/test/support/integration/adapters/delayed_job.rb
@@ -0,0 +1,14 @@
+module DelayedJobJobsManager
+ def clear_jobs
+ Delayed::Job.delete_all
+ end
+
+ def start_workers
+ @worker = Delayed::Worker.new(quiet: false, sleep_delay: 0.5)
+ @thread = Thread.new { @worker.start }
+ end
+
+ def stop_workers
+ @worker.stop
+ end
+end
diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb
new file mode 100644
index 0000000000..12d063ea0d
--- /dev/null
+++ b/activejob/test/support/integration/adapters/qu.rb
@@ -0,0 +1,14 @@
+module QuJobsManager
+ def clear_jobs
+ Qu.clear "active_jobs_default"
+ end
+
+ def start_workers
+ @thread = Thread.new { Qu::Worker.new("active_jobs_default").start }
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
new file mode 100644
index 0000000000..f15c9af910
--- /dev/null
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -0,0 +1,19 @@
+module QueJobsManager
+ def clear_jobs
+ Que.clear!
+ end
+
+ def start_workers
+ @thread = Thread.new do
+ loop do
+ Que::Job.work("active_jobs_default")
+ sleep 0.5
+ end
+ end
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
new file mode 100644
index 0000000000..3b24eca5b9
--- /dev/null
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -0,0 +1,21 @@
+module QC; WAIT_TIME = 0.5; end
+
+module QueueClassicJobsManager
+ def clear_jobs
+ # disabling this as it locks
+ # QC::Queue.new("active_jobs_default").delete_all
+ end
+
+ def start_workers
+ @pid = fork do
+ QC::Conn.connection = QC::Conn.connect
+ worker = QC::Worker.new(q_name: 'active_jobs_default')
+ worker.start
+ end
+ end
+
+ def stop_workers
+ Process.kill 'HUP', @pid
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
new file mode 100644
index 0000000000..b9811bd3e1
--- /dev/null
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -0,0 +1,18 @@
+module ResqueJobsManager
+ def clear_jobs
+ Resque.queues.each { |queue_name| Resque.redis.del "queue:#{queue_name}" }
+ Resque.redis.keys("delayed:*").each { |key| Resque.redis.del "#{key}" }
+ Resque.redis.del "delayed_queue_schedule"
+ end
+
+ def start_workers
+ @thread = Thread.new do
+ Resque::Worker.new("*").work(0.5)
+ end
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
new file mode 100644
index 0000000000..d3b7d15614
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -0,0 +1,19 @@
+require 'sidekiq/launcher'
+require 'sidekiq/api'
+
+module SidekiqJobsManager
+ def clear_jobs
+ Sidekiq::Queue.new("active_jobs_default").clear
+ end
+
+ def start_workers
+ options = {:queues=>["active_jobs_default"], :concurrency=>1, :environment=>"test", :timeout=>8, :daemon=>true, :strict=>true}
+ @launcher = Sidekiq::Launcher.new(options)
+ @launcher.run
+ end
+
+ def stop_workers
+ @launcher.stop
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb
new file mode 100644
index 0000000000..5dcab68515
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sneakers.rb
@@ -0,0 +1,18 @@
+require 'sneakers/runner'
+
+module SneakersJobsManager
+ def clear_jobs
+ end
+
+ def start_workers
+ cmd = %{cd #{Rails.root.to_s} && (RAILS_ENV=test AJADAPTER=sneakers WORKERS=ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper bundle exec rake --trace sneakers:run)}
+ `#{cmd}`
+ while !Rails.root.join("tmp/sneakers.pid").exist? do
+ sleep 0.5
+ end
+ end
+
+ def stop_workers
+ Process.kill 'TERM', File.open(Rails.root.join("tmp/sneakers.pid").to_s).read.to_i
+ end
+end
diff --git a/activejob/test/support/integration/adapters/sucker_punch.rb b/activejob/test/support/integration/adapters/sucker_punch.rb
new file mode 100644
index 0000000000..317f9c80fd
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sucker_punch.rb
@@ -0,0 +1,5 @@
+module SuckerPunchJobsManager
+ def clear_jobs
+ end
+end
+
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
new file mode 100644
index 0000000000..cb94e7cfb5
--- /dev/null
+++ b/activejob/test/support/integration/helper.rb
@@ -0,0 +1,12 @@
+ENV["RAILS_ENV"] = "test"
+require File.expand_path("../../../dummy/config/environment.rb", __FILE__)
+require "rails/test_help"
+Rails.backtrace_cleaner.remove_silencers!
+
+require_relative 'test_case_helpers'
+ActiveSupport::TestCase.send(:include, TestCaseHelpers)
+
+JobsManager.current_manager.setup
+JobsManager.current_manager.start_workers
+Minitest.after_run { JobsManager.current_manager.stop_workers }
+
diff --git a/activejob/test/support/integration/jobs_manager.rb b/activejob/test/support/integration/jobs_manager.rb
new file mode 100644
index 0000000000..1da74193b1
--- /dev/null
+++ b/activejob/test/support/integration/jobs_manager.rb
@@ -0,0 +1,23 @@
+class JobsManager
+ @@managers = {}
+ attr :adapter_name
+
+ def self.current_manager
+ @@managers[ENV['AJADAPTER']] ||= new(ENV['AJADAPTER'])
+ end
+
+ def initialize(adapter_name)
+ @adapter_name = adapter_name
+ require_relative "adapters/#{adapter_name}"
+ extend "#{adapter_name.camelize}JobsManager".constantize
+ end
+
+ def setup
+ end
+
+ def start_workers
+ end
+
+ def stop_workers
+ end
+end
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
new file mode 100644
index 0000000000..9a5eea0783
--- /dev/null
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -0,0 +1,30 @@
+require 'active_support/concern'
+require 'support/integration/jobs_manager'
+
+module TestCaseHelpers
+ extend ActiveSupport::Concern
+
+ included do
+ self.use_transactional_fixtures = false
+
+ setup do
+ clear_jobs
+ end
+
+ teardown do
+ clear_jobs
+ FileUtils.rm_rf Dir[Dummy::Application.root.join("tmp/AJ-*")]
+ end
+ end
+
+ protected
+
+ def jobs_manager
+ JobsManager.current_manager
+ end
+
+ def clear_jobs
+ jobs_manager.clear_jobs
+ end
+
+end
diff --git a/activejob/test/support/queue_classic/inline.rb b/activejob/test/support/queue_classic/inline.rb
new file mode 100644
index 0000000000..5e9c295e01
--- /dev/null
+++ b/activejob/test/support/queue_classic/inline.rb
@@ -0,0 +1,11 @@
+require 'queue_classic'
+
+module QC
+ class Queue
+ def enqueue(method, *args)
+ receiver_str, _, message = method.rpartition('.')
+ receiver = eval(receiver_str)
+ receiver.send(message, *args)
+ end
+ end
+end
diff --git a/activejob/test/support/sneakers/inline.rb b/activejob/test/support/sneakers/inline.rb
new file mode 100644
index 0000000000..16d9b830fa
--- /dev/null
+++ b/activejob/test/support/sneakers/inline.rb
@@ -0,0 +1,12 @@
+require 'sneakers'
+
+module Sneakers
+ module Worker
+ module ClassMethods
+ def enqueue(msg)
+ worker = self.new(nil, nil, {})
+ worker.work(*msg)
+ end
+ end
+ end
+end