sidetiq 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ lib/*.bundle
2
+ coverage
3
+ rdoc
4
+ doc
5
+ .yardoc
6
+ .bundle
7
+ Gemfile.lock
8
+ pkg
9
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ before_script:
6
+ - bundle exec rake compile
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rake'
5
+ gem 'rake-compiler'
6
+ gem 'sinatra', require: false
7
+ gem 'slim', require: false
8
+ end
9
+
10
+ group :test do
11
+ gem 'simplecov'
12
+ gem 'mocha'
13
+ gem 'rack-test'
14
+ end
15
+
16
+ gemspec
17
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2012 by Tobias Svensson <tobias@musicglue.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,146 @@
1
+ Sidetiq
2
+ =======
3
+
4
+ [![Build Status](https://travis-ci.org/tobiassvn/sidetiq.png)](https://travis-ci.org/tobiassvn/sidetiq)
5
+
6
+ Recurring jobs for [Sidekiq](http://mperham.github.com/sidekiq/).
7
+
8
+ ## DESCRIPTION
9
+
10
+ Sidetiq provides a simple API for defining recurring workers for Sidekiq.
11
+
12
+ - Flexible DSL based on [ice_cube](http://seejohnrun.github.com/ice_cube/)
13
+
14
+ - High-resolution timer using `clock_gettime(3)` (or `mach_absolute_time()` on
15
+ Apple Mac OS X), allowing for accurate sub-second clock ticks.
16
+
17
+ - Sidetiq uses a locking mechanism (based on `setnx` and `pexpire`) internally
18
+ so Sidetiq clocks can run in each Sidekiq process without interfering with
19
+ each other (tested with sub-second polling of scheduled jobs by Sidekiq and
20
+ Sidetiq clock rates above 100hz).
21
+
22
+ ## DEPENDENCIES
23
+
24
+ - [Sidekiq](http://mperham.github.com/sidekiq/)
25
+ - [ice_cube](http://seejohnrun.github.com/ice_cube/)
26
+
27
+ ## INSTALLATION
28
+
29
+ The best way to install Sidetiq is with RubyGems:
30
+
31
+ $ [sudo] gem install sidetiq
32
+
33
+ If you're installing from source, you can use [Bundler](http://gembundler.com/)
34
+ to pick up all the gems ([more info](http://gembundler.com/bundle_install.html)):
35
+
36
+ $ bundle install
37
+
38
+ ## GETTING STARTED
39
+
40
+ Defining recurring jobs is simple:
41
+
42
+ ```ruby
43
+ class MyWorker
44
+ include Sidekiq::Worker
45
+ include Sidetiq::Schedulable
46
+
47
+ # Daily at midnight
48
+ tiq { daily }
49
+ end
50
+ ```
51
+
52
+ It also is possible to define multiple scheduling rules for a worker:
53
+
54
+ ```ruby
55
+ class MyWorker
56
+ include Sidekiq::Worker
57
+ include Sidetiq::Schedulable
58
+
59
+ tiq do
60
+ # Every third year in March
61
+ yearly(3).month_of_year(:march)
62
+
63
+ # Every second year in February
64
+ yearly(2).month_of_year(:february)
65
+ end
66
+ end
67
+ ```
68
+
69
+ Or complex schedules:
70
+
71
+ ```ruby
72
+ class MyWorker
73
+ include Sidekiq::Worker
74
+ include Sidetiq::Schedulable
75
+
76
+ # Every other month on the first monday and last tuesday at 12 o'clock.
77
+ tiq { monthly(2).day_of_week(1 => [1], 2 => [-1]).hour_of_day(12) }
78
+ end
79
+ ```
80
+
81
+ To start Sidetiq, simply call `Sidetiq::Clock.start!` in a server specific
82
+ configuration block:
83
+
84
+ ```ruby
85
+ Sidekiq.configure_server do |config|
86
+ Sidetiq::Clock.start!
87
+ end
88
+ ```
89
+
90
+ Additionally, Sidetiq includes a middleware that will check if the clock
91
+ thread is still alive and restart it if necessary.
92
+
93
+ ## CONFIGURATION
94
+
95
+ ```ruby
96
+ Sidetiq.configure do |config|
97
+ # Thread priority of the clock thread (default: Thread.main.priority as
98
+ # defined when Sidetiq is loaded).
99
+ config.priority = 2
100
+
101
+ # Clock tick resolution in seconds (default: 1).
102
+ config.resolution = 0.5
103
+
104
+ # Clock locking key expiration in ms (default: 1000).
105
+ config.lock_expire = 100
106
+
107
+ # When `true` uses UTC instead of local times (default: false)
108
+ config.utc = false
109
+ end
110
+ ```
111
+
112
+ ## WEB EXTENSION
113
+
114
+ Sidetiq includes an extension for Sidekiq's web interface. It will not be
115
+ loaded by default, so it will have to be required manually:
116
+
117
+ ```ruby
118
+ require 'sidetiq/web'
119
+ ```
120
+
121
+ ### SCREENSHOT
122
+
123
+ ![Screenshot](http://f.cl.ly/items/1P2u1v091F3V1n381g2I/Screen%20Shot%202013-02-01%20at%2012.16.17.png)
124
+
125
+ ## CONTRIBUTE
126
+
127
+ If you'd like to contribute to Sidetiq, start by forking my repo on GitHub:
128
+
129
+ [http://github.com/tobiassvn/sidetiq](http://github.com/tobiassvn/sidetiq)
130
+
131
+ To get all of the dependencies, install the gem first. The best way to get
132
+ your changes merged back into core is as follows:
133
+
134
+ 1. Clone down your fork
135
+ 1. Create a thoughtfully named topic branch to contain your change
136
+ 1. Write some code
137
+ 1. Add tests and make sure everything still passes by running `rake`
138
+ 1. If you are adding new functionality, document it in the README
139
+ 1. Do not change the version number, I will do that on my end
140
+ 1. If necessary, rebase your commits into logical chunks, without errors
141
+ 1. Push the branch up to GitHub
142
+ 1. Send a pull request to the tobiassvn/sidetiq project.
143
+
144
+ ## LICENSE
145
+
146
+ Sidetiq is released under the MIT License. See LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rake/extensiontask'
4
+
5
+ Rake::ExtensionTask.new('sidetiq_ext')
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.pattern = 'test/**/test_*.rb'
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,18 @@
1
+ # Run with `sidekiq -r /path/to/simple.rb`
2
+
3
+ require 'sidekiq'
4
+ require 'sidetiq'
5
+
6
+ Sidekiq.options[:poll_interval] = 1
7
+
8
+ class MyWorker
9
+ include Sidekiq::Worker
10
+ include Sidetiq::Schedulable
11
+
12
+ tiq { secondly }
13
+
14
+ def perform(*args)
15
+ Sidekiq.logger.info "#perform"
16
+ end
17
+ end
18
+
@@ -0,0 +1,4 @@
1
+ require 'rbconfig'
2
+ require 'mkmf'
3
+ create_makefile('sidetiq_ext')
4
+
@@ -0,0 +1,97 @@
1
+ #include <ruby.h>
2
+ #include <assert.h>
3
+ #include "sidetiq_ext.h"
4
+
5
+ #ifdef __APPLE__
6
+ #include <sys/time.h>
7
+ #include <sys/resource.h>
8
+ #include <mach/mach.h>
9
+ #include <mach/clock.h>
10
+ #include <mach/mach_time.h>
11
+ #include <errno.h>
12
+ #include <unistd.h>
13
+ #include <sched.h>
14
+ #else
15
+ #include <time.h>
16
+ #endif
17
+
18
+ VALUE msidetiq;
19
+ VALUE esidetiq_error;
20
+ VALUE csidetiq_clock;
21
+
22
+ #ifdef __APPLE__
23
+ static mach_timebase_info_data_t clock_gettime_inf;
24
+
25
+ typedef enum {
26
+ CLOCK_REALTIME,
27
+ CLOCK_MONOTONIC,
28
+ CLOCK_PROCESS_CPUTIME_ID,
29
+ CLOCK_THREAD_CPUTIME_ID
30
+ } clockid_t;
31
+
32
+ int clock_gettime(clockid_t clk_id, struct timespec *tp)
33
+ {
34
+ kern_return_t ret;
35
+ clock_serv_t clk;
36
+ clock_id_t clk_serv_id;
37
+ mach_timespec_t tm;
38
+ uint64_t start, end, delta, nano;
39
+ int retval = -1;
40
+
41
+ switch (clk_id) {
42
+ case CLOCK_REALTIME:
43
+ case CLOCK_MONOTONIC:
44
+ clk_serv_id = clk_id == CLOCK_REALTIME ? CALENDAR_CLOCK : SYSTEM_CLOCK;
45
+ if (KERN_SUCCESS == (ret = host_get_clock_service(mach_host_self(), clk_serv_id, &clk))) {
46
+ if (KERN_SUCCESS == (ret = clock_get_time(clk, &tm))) {
47
+ tp->tv_sec = tm.tv_sec;
48
+ tp->tv_nsec = tm.tv_nsec;
49
+ retval = 0;
50
+ }
51
+ }
52
+ if (KERN_SUCCESS != ret) {
53
+ errno = EINVAL;
54
+ retval = -1;
55
+ }
56
+ break;
57
+ case CLOCK_PROCESS_CPUTIME_ID:
58
+ case CLOCK_THREAD_CPUTIME_ID:
59
+ start = mach_absolute_time();
60
+ if (clk_id == CLOCK_PROCESS_CPUTIME_ID) {
61
+ getpid();
62
+ } else {
63
+ sched_yield();
64
+ }
65
+ end = mach_absolute_time();
66
+ delta = end - start;
67
+ if (0 == clock_gettime_inf.denom) {
68
+ mach_timebase_info(&clock_gettime_inf);
69
+ }
70
+ nano = delta * clock_gettime_inf.numer / clock_gettime_inf.denom;
71
+ tp->tv_sec = nano * 1e-9;
72
+ tp->tv_nsec = nano - (tp->tv_sec * 1e9);
73
+ retval = 0;
74
+ break;
75
+ default:
76
+ errno = EINVAL;
77
+ retval = -1;
78
+ }
79
+ return retval;
80
+ }
81
+ #endif
82
+
83
+ static VALUE sidetiq_gettime(VALUE self)
84
+ {
85
+ struct timespec time;
86
+ assert(clock_gettime(CLOCK_REALTIME, &time) == 0);
87
+ return rb_time_nano_new(time.tv_sec, time.tv_nsec);
88
+ }
89
+
90
+ void Init_sidetiq_ext()
91
+ {
92
+ msidetiq = rb_define_module("Sidetiq");
93
+ esidetiq_error = rb_define_class_under(msidetiq, "Error", rb_eStandardError);
94
+ csidetiq_clock = rb_define_class_under(msidetiq, "Clock", rb_cObject);
95
+ rb_define_private_method(csidetiq_clock, "clock_gettime", sidetiq_gettime, 0);
96
+ }
97
+
@@ -0,0 +1,17 @@
1
+ #ifndef __SIDETIQ_EXT_H__
2
+ #define __SIDETIQ_EXT_H__
3
+ #include <ruby.h>
4
+
5
+ void Init_sidetiq_ext();
6
+ static VALUE sidetiq_gettime(VALUE self);
7
+
8
+ /* module Sidetiq */
9
+ extern VALUE msidetiq;
10
+
11
+ /* class Sidetiq::Error < StandardError */
12
+ extern VALUE esidetiq_error;
13
+
14
+ /* class Sidetiq::Clock */
15
+ extern VALUE csidetiq_clock;
16
+
17
+ #endif
@@ -0,0 +1,105 @@
1
+ module Sidetiq
2
+ configure do |config|
3
+ config.priority = Thread.main.priority
4
+ config.resolution = 1
5
+ config.lock_expire = 1000
6
+ config.utc = false
7
+ end
8
+
9
+ class Clock
10
+ include Singleton
11
+ include MonitorMixin
12
+
13
+ START_TIME = Sidetiq.config.utc ? Time.utc(2010, 1, 1) : Time.local(2010, 1, 1)
14
+
15
+ attr_reader :schedules, :thread
16
+
17
+ def self.method_missing(meth, *args, &block)
18
+ instance.__send__(meth, *args, &block)
19
+ end
20
+
21
+ def initialize
22
+ super
23
+ @schedules = {}
24
+ end
25
+
26
+ def schedule_for(worker)
27
+ schedules[worker] ||= Sidetiq::Schedule.new(START_TIME)
28
+ end
29
+
30
+ def tick
31
+ @tick = gettime
32
+ synchronize do
33
+ schedules.each do |worker, schedule|
34
+ if schedule.schedule_next?(@tick)
35
+ enqueue(worker, schedule.next_occurrence(@tick))
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def gettime
42
+ Sidetiq.config.utc ? clock_gettime.utc : clock_gettime
43
+ end
44
+
45
+ def start!
46
+ return if ticking?
47
+
48
+ Sidekiq.logger.info "Sidetiq::Clock start"
49
+ @thread = Thread.start { clock { tick } }
50
+ @thread.abort_on_exception = true
51
+ @thread.priority = Sidetiq.config.resolution
52
+ end
53
+
54
+ def stop!
55
+ if ticking?
56
+ @thread.kill
57
+ Sidekiq.logger.info "Sidetiq::Clock stop"
58
+ end
59
+ end
60
+
61
+ def ticking?
62
+ @thread && @thread.alive?
63
+ end
64
+
65
+ private
66
+
67
+ def enqueue(worker, time)
68
+ key = "sidetiq:#{worker.name}"
69
+
70
+ synchronize_clockworks("#{key}:lock") do |redis|
71
+ status = redis.get(key)
72
+
73
+ if status.nil? || status.to_f < time.to_f
74
+ time_f = time.to_f
75
+ Sidekiq.logger.info "Sidetiq::Clock enqueue #{worker.name} (at: #{time_f})"
76
+ redis.set(key, time_f)
77
+ worker.perform_at(time)
78
+ end
79
+ end
80
+ end
81
+
82
+ def synchronize_clockworks(lock)
83
+ Sidekiq.redis do |redis|
84
+ if redis.setnx(lock, 1)
85
+ Sidekiq.logger.debug "Sidetiq::Clock lock #{lock} #{Thread.current.inspect}"
86
+
87
+ redis.pexpire(lock, Sidetiq.config.lock_expire)
88
+ yield redis
89
+ redis.del(lock)
90
+
91
+ Sidekiq.logger.debug "Sidetiq::Clock unlock #{lock} #{Thread.current.inspect}"
92
+ end
93
+ end
94
+ end
95
+
96
+ def clock
97
+ loop do
98
+ yield
99
+ Thread.pass
100
+ sleep Sidetiq.config.resolution
101
+ end
102
+ end
103
+ end
104
+ end
105
+
@@ -0,0 +1,14 @@
1
+ module Sidetiq
2
+ class << self
3
+ attr_writer :config
4
+
5
+ def configure
6
+ yield config
7
+ end
8
+
9
+ def config
10
+ @config ||= OpenStruct.new
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,20 @@
1
+ module Sidetiq
2
+ class Middleware
3
+ def initialize
4
+ @clock = Sidetiq::Clock.instance
5
+ end
6
+
7
+ def call(*args)
8
+ # Restart the clock if the thread died.
9
+ @clock.start! if !@clock.ticking?
10
+ yield
11
+ end
12
+ end
13
+ end
14
+
15
+ Sidekiq.configure_server do |config|
16
+ config.server_middleware do |chain|
17
+ chain.add Sidetiq::Middleware
18
+ end
19
+ end
20
+
@@ -0,0 +1,17 @@
1
+ module Sidetiq
2
+ module Schedulable
3
+ module ClassMethods
4
+ def tiq(&block)
5
+ clock = Sidetiq::Clock.instance
6
+ clock.synchronize do
7
+ clock.schedule_for(self).instance_eval(&block)
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.included(klass)
13
+ klass.extend(Sidetiq::Schedulable::ClassMethods)
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,22 @@
1
+ module Sidetiq
2
+ class Schedule < IceCube::Schedule
3
+ def method_missing(meth, *args, &block)
4
+ if IceCube::Rule.respond_to?(meth)
5
+ rule = IceCube::Rule.send(meth, *args, &block)
6
+ add_recurrence_rule(rule)
7
+ rule
8
+ else
9
+ super
10
+ end
11
+ end
12
+
13
+ def schedule_next?(time)
14
+ if @last_scheduled != (no = next_occurrence(time))
15
+ @last_scheduled = no
16
+ return true
17
+ end
18
+ false
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,10 @@
1
+ module Sidetiq
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 2
6
+
7
+ STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ end
10
+
@@ -0,0 +1,23 @@
1
+ header.row
2
+ .span5
3
+ h3 Recurring Jobs
4
+
5
+ - if @schedules.length > 0
6
+ table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
7
+ thead
8
+ th style="width: 50%" Worker
9
+ th style="width: 20%" Queue
10
+ th style="width: 20%" Next Run
11
+ th style="width: 10%" Actions
12
+ - @schedules.each do |worker, schedule|
13
+ tr
14
+ td
15
+ = worker.name
16
+ td= worker.get_sidekiq_options['queue']
17
+ td
18
+ == relative_time(schedule.next_occurrence(@time))
19
+ td
20
+ a href="#{root_path}sidetiq/#{worker.name}" Details
21
+ - else
22
+ .alert.alert-success No recurring jobs found.
23
+
@@ -0,0 +1,40 @@
1
+ header.row
2
+ .span5
3
+ h3
4
+ ' Recurring Job:
5
+ = @worker.name
6
+
7
+ - if (recurrences = @schedule.recurrence_rules).length > 0
8
+ table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
9
+ thead
10
+ th Recurrences
11
+ - recurrences.each do |rule|
12
+ tr
13
+ td
14
+ = rule.to_s
15
+
16
+ br
17
+
18
+ - if (exceptions = @schedule.exception_rules).length > 0
19
+ table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
20
+ thead
21
+ th Exceptions
22
+ - exceptions.each do |rule|
23
+ tr
24
+ td
25
+ = rule.to_s
26
+
27
+ br
28
+
29
+ table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;"
30
+ thead
31
+ th style="width: 25%" Next 10 runs
32
+ th style="width: 75%"
33
+ - @schedule.next_occurrences(10, @time).each do |time|
34
+ tr
35
+ td
36
+ time= time.getutc
37
+ td
38
+ == relative_time(time)
39
+
40
+ br
@@ -0,0 +1,47 @@
1
+ require 'sidekiq/web'
2
+
3
+ module Sidetiq
4
+ module Web
5
+
6
+ def self.registered(app)
7
+ app.helpers do
8
+ def find_template(view, *args, &block)
9
+ path = File.expand_path(File.join('..', 'views'), __FILE__)
10
+ super(path, *args, &block)
11
+ super
12
+ end
13
+ end
14
+
15
+ app.get "/sidetiq" do
16
+ clock = Sidetiq::Clock.instance
17
+ @schedules = clock.schedules
18
+ @time = clock.gettime
19
+ slim :sidetiq
20
+ end
21
+
22
+ app.get "/sidetiq/:name" do
23
+ halt 404 unless (name = params[:name])
24
+
25
+ clock = Sidetiq::Clock.instance
26
+ schedules = clock.schedules
27
+
28
+ @time = clock.gettime
29
+
30
+ @worker, @schedule = schedules.select do |worker, schedule|
31
+ worker.name == name
32
+ end.flatten
33
+
34
+ slim :sidetiq_details
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Sidekiq::Web.register(Sidetiq::Web)
41
+
42
+ if Sidekiq::Web.tabs.is_a?(Array)
43
+ Sidekiq::Web.tabs << "sidetiq"
44
+ else
45
+ Sidekiq::Web.tabs["Sidetiq"] = "sidetiq"
46
+ end
47
+
data/lib/sidetiq.rb ADDED
@@ -0,0 +1,20 @@
1
+ # stdlib
2
+ require 'monitor'
3
+ require 'ostruct'
4
+ require 'singleton'
5
+
6
+ # gems
7
+ require 'ice_cube'
8
+ require 'sidekiq'
9
+
10
+ # c extensions
11
+ require 'sidetiq_ext'
12
+
13
+ # internal
14
+ require 'sidetiq/config'
15
+ require 'sidetiq/clock'
16
+ require 'sidetiq/middleware'
17
+ require 'sidetiq/schedule'
18
+ require 'sidetiq/schedulable'
19
+ require 'sidetiq/version'
20
+
data/sidetiq.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path(File.join('..', 'lib'), __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sidetiq/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "sidetiq"
8
+ gem.version = Sidetiq::VERSION::STRING
9
+ gem.authors = ["Tobias Svensson"]
10
+ gem.email = ["tob@tobiassvensson.co.uk"]
11
+ gem.description = "High-resolution job scheduler for Sidekiq"
12
+ gem.summary = gem.description
13
+ gem.homepage = "http://github.com/tobiassvn/sidetiq"
14
+ gem.license = "MIT"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+ gem.extensions = ['ext/sidetiq_ext/extconf.rb']
21
+
22
+ gem.add_dependency 'sidekiq'
23
+ gem.add_dependency 'ice_cube'
24
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'simplecov'
2
+ SimpleCov.start { add_filter "/test/" }
3
+
4
+ require 'minitest/autorun'
5
+ require 'mocha/setup'
6
+ require 'rack/test'
7
+
8
+ require 'sidekiq'
9
+ require 'sidekiq/testing'
10
+
11
+ require 'sidetiq'
12
+ require 'sidetiq/web'
13
+
14
+ # Keep the test output clean
15
+ Sidekiq.logger = Logger.new(nil)
16
+
17
+ class Sidetiq::TestCase < MiniTest::Unit::TestCase
18
+ def setup
19
+ Sidekiq.redis { |r| r.flushall }
20
+ end
21
+
22
+ def clock
23
+ @clock ||= Sidetiq::Clock.instance
24
+ end
25
+ end
26
+
@@ -0,0 +1,65 @@
1
+ require_relative 'helper'
2
+
3
+ class TestClock < Sidetiq::TestCase
4
+ class FakeWorker;
5
+ end
6
+
7
+ def test_delegates_to_instance
8
+ Sidetiq::Clock.instance.expects(:foo).once
9
+ Sidetiq::Clock.foo
10
+ end
11
+
12
+ def test_start_stop
13
+ refute clock.ticking?
14
+ assert_nil clock.thread
15
+
16
+ clock.start!
17
+ Thread.pass
18
+ sleep 0.01
19
+
20
+ assert clock.ticking?
21
+ assert_kind_of Thread, clock.thread
22
+
23
+ clock.stop!
24
+ Thread.pass
25
+ sleep 0.01
26
+
27
+ refute clock.ticking?
28
+ refute clock.thread.alive?
29
+ end
30
+
31
+ def test_gettime_seconds
32
+ assert_equal clock.gettime.tv_sec, Time.now.tv_sec
33
+ end
34
+
35
+ def test_gettime_nsec
36
+ refute_nil clock.gettime.tv_nsec
37
+ end
38
+
39
+ def test_gettime_utc
40
+ refute clock.gettime.utc?
41
+ Sidetiq.config.utc = true
42
+ assert clock.gettime.utc?
43
+ Sidetiq.config.utc = false
44
+ end
45
+
46
+ def test_enqueues_jobs_by_schedule
47
+ schedule = Sidetiq::Schedule.new(Sidetiq::Clock::START_TIME)
48
+ schedule.daily
49
+
50
+ clock.stubs(:schedules).returns(FakeWorker => schedule)
51
+
52
+ FakeWorker.expects(:perform_at).times(10)
53
+
54
+ 10.times do |i|
55
+ clock.stubs(:gettime).returns(Time.local(2011, 1, i + 1, 1))
56
+ clock.tick
57
+ end
58
+
59
+ clock.stubs(:gettime).returns(Time.local(2011, 1, 10, 2))
60
+ clock.tick
61
+ clock.tick
62
+ clock.tick
63
+ end
64
+ end
65
+
@@ -0,0 +1,21 @@
1
+ require_relative 'helper'
2
+
3
+ class TestConfig < Sidetiq::TestCase
4
+ def setup
5
+ @saved = Sidetiq.config
6
+ Sidetiq.config = OpenStruct.new
7
+ end
8
+
9
+ def teardown
10
+ Sidetiq.config = @saved
11
+ end
12
+
13
+ def test_configure
14
+ Sidetiq.configure do |config|
15
+ config.test = 42
16
+ end
17
+
18
+ assert_equal 42, Sidetiq.config.test
19
+ end
20
+ end
21
+
@@ -0,0 +1,7 @@
1
+ require_relative 'helper'
2
+
3
+ class TestErrors < Sidetiq::TestCase
4
+ def test_error_superclass
5
+ assert_equal StandardError, Sidetiq::Error.superclass
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'helper'
2
+
3
+ class TestMiddleware < Sidetiq::TestCase
4
+ def middleware
5
+ Sidetiq::Middleware.new
6
+ end
7
+
8
+ def test_restarts_clock
9
+ clock.stubs(:ticking?).returns(false)
10
+ clock.expects(:start!).once
11
+ middleware.call {}
12
+
13
+ clock.stubs(:ticking?).returns(true)
14
+ clock.expects(:start!).never
15
+ middleware.call {}
16
+ end
17
+ end
18
+
@@ -0,0 +1,25 @@
1
+ require_relative 'helper'
2
+
3
+ class TestSchedule < Sidetiq::TestCase
4
+ def test_super
5
+ assert_equal IceCube::Schedule, Sidetiq::Schedule.superclass
6
+ end
7
+
8
+ def test_method_missing
9
+ sched = Sidetiq::Schedule.new
10
+ sched.daily
11
+ assert_equal "Daily", sched.to_s
12
+ end
13
+
14
+ def test_schedule_next?
15
+ sched = Sidetiq::Schedule.new
16
+
17
+ sched.daily
18
+
19
+ assert sched.schedule_next?(Time.now + (24 * 60 * 60))
20
+ refute sched.schedule_next?(Time.now + (24 * 60 * 60))
21
+ assert sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
22
+ refute sched.schedule_next?(Time.now + (2 * 24 * 60 * 60))
23
+ end
24
+ end
25
+
@@ -0,0 +1,21 @@
1
+ require_relative 'helper'
2
+
3
+ class TestVersion < Sidetiq::TestCase
4
+ def test_major
5
+ assert_instance_of Fixnum, Sidetiq::VERSION::MAJOR
6
+ end
7
+
8
+ def test_minor
9
+ assert_instance_of Fixnum, Sidetiq::VERSION::MINOR
10
+ end
11
+
12
+ def test_patch
13
+ assert_instance_of Fixnum, Sidetiq::VERSION::PATCH
14
+ end
15
+
16
+ def test_string
17
+ assert_equal Sidetiq::VERSION::STRING, [Sidetiq::VERSION::MAJOR,
18
+ Sidetiq::VERSION::MINOR, Sidetiq::VERSION::PATCH].compact.join('.')
19
+ end
20
+ end
21
+
data/test/test_web.rb ADDED
@@ -0,0 +1,58 @@
1
+ require_relative 'helper'
2
+
3
+ class TestWeb < Sidetiq::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ class Worker
7
+ include Sidekiq::Worker
8
+ include Sidetiq::Schedulable
9
+
10
+ tiq do
11
+ daily(1)
12
+ yearly(2)
13
+ monthly(3)
14
+
15
+ add_exception_rule yearly.month_of_year(:february)
16
+ end
17
+ end
18
+
19
+ def app
20
+ Sidekiq::Web
21
+ end
22
+
23
+ def test_home_tab
24
+ get '/'
25
+ assert_equal 200, last_response.status
26
+ assert_match last_response.body, /Sidekiq/
27
+ assert_match last_response.body, /Sidetiq/
28
+ end
29
+
30
+ def test_sidetiq_page
31
+ get '/sidetiq'
32
+ assert_equal 200, last_response.status
33
+
34
+ clock.schedules.each do |worker, schedule|
35
+ assert_match last_response.body, /#{worker.name}/
36
+ assert_match last_response.body, /#{worker.get_sidekiq_options['queue']}/
37
+ end
38
+ end
39
+
40
+ def test_details_page
41
+ get "/sidetiq/#{Worker.name}"
42
+ assert_equal 200, last_response.status
43
+ schedule = clock.schedules[Worker]
44
+
45
+ schedule.recurrence_rules.each do |rule|
46
+ assert_match last_response.body, /#{rule.to_s}/
47
+ end
48
+
49
+ schedule.exception_rules.each do |rule|
50
+ assert_match last_response.body, /#{rule.to_s}/
51
+ end
52
+
53
+ schedule.next_occurrences(10).each do |time|
54
+ assert_match last_response.body, /#{time.getutc.to_s}/
55
+ end
56
+ end
57
+ end
58
+
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidetiq
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.2
6
+ platform: ruby
7
+ authors:
8
+ - Tobias Svensson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ none: false
21
+ name: sidekiq
22
+ type: :runtime
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ none: false
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ none: false
37
+ name: ice_cube
38
+ type: :runtime
39
+ prerelease: false
40
+ requirement: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ none: false
46
+ description: High-resolution job scheduler for Sidekiq
47
+ email:
48
+ - tob@tobiassvensson.co.uk
49
+ executables: []
50
+ extensions:
51
+ - ext/sidetiq_ext/extconf.rb
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .travis.yml
56
+ - Gemfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - examples/simple.rb
61
+ - ext/sidetiq_ext/extconf.rb
62
+ - ext/sidetiq_ext/sidetiq_ext.c
63
+ - ext/sidetiq_ext/sidetiq_ext.h
64
+ - lib/sidetiq.rb
65
+ - lib/sidetiq/clock.rb
66
+ - lib/sidetiq/config.rb
67
+ - lib/sidetiq/middleware.rb
68
+ - lib/sidetiq/schedulable.rb
69
+ - lib/sidetiq/schedule.rb
70
+ - lib/sidetiq/version.rb
71
+ - lib/sidetiq/views/sidetiq.slim
72
+ - lib/sidetiq/views/sidetiq_details.slim
73
+ - lib/sidetiq/web.rb
74
+ - sidetiq.gemspec
75
+ - test/helper.rb
76
+ - test/test_clock.rb
77
+ - test/test_config.rb
78
+ - test/test_errors.rb
79
+ - test/test_middleware.rb
80
+ - test/test_schedule.rb
81
+ - test/test_version.rb
82
+ - test/test_web.rb
83
+ homepage: http://github.com/tobiassvn/sidetiq
84
+ licenses:
85
+ - MIT
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ none: false
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ none: false
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.23
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: High-resolution job scheduler for Sidekiq
108
+ test_files:
109
+ - test/helper.rb
110
+ - test/test_clock.rb
111
+ - test/test_config.rb
112
+ - test/test_errors.rb
113
+ - test/test_middleware.rb
114
+ - test/test_schedule.rb
115
+ - test/test_version.rb
116
+ - test/test_web.rb
117
+ has_rdoc: