zhong 0.1.5 → 0.1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4eafb64d51589613dfcfeca62326319eb839962f
4
- data.tar.gz: 66b4fd921dd9ed1795ffa407afbcc5f33b867607
3
+ metadata.gz: 8f1dac5a4ab2bbf39455eb3ae3aa85b8db281f71
4
+ data.tar.gz: bad4a27f7f28e8d9e09bd89e722c8d14de24135f
5
5
  SHA512:
6
- metadata.gz: 267619b1f313f958d2e2e7071bb3fbdcb22efcd9018e64d1cdcf5f07d86d055e5e4e4e87f8b1144550956ded9370c10584212fec1c6c7890bd6ace880af2348b
7
- data.tar.gz: e4a9329e22811d4cda8f01882ed7e8755388b30723eb0a424f5b01ff05d526f512be60fae222d77b359326e16f60208cc8452b0ce837b9ad2ea492e9d17bf8a6
6
+ metadata.gz: 09b25ced5faf77601dcc4056f32441cdf9183a542e4fcdc87ce689806e38e1fc1189613ff9ca11f776c8967e1c4fba9bab83049022860c21ab4893255c64fa4b
7
+ data.tar.gz: 50d880d5819a8d103d99740a1cacd3a828277015d0def4d486fa777d20f082daa7c1e9d6a56f89469de22f3d9c26245b056165fad4f2339b7fcc45647471a4fd
data/.codeclimate.yml CHANGED
@@ -16,8 +16,8 @@ ratings:
16
16
  paths:
17
17
  - "**.js"
18
18
  - "**.jsx"
19
- - "**.module"
20
19
  - "**.rb"
21
20
  exclude_paths:
21
+ - "**/*helper*"
22
22
  - test/
23
23
  - "**/*{.,-}min.js"
data/.rubocop.yml CHANGED
@@ -74,7 +74,7 @@ Style/SpaceInsideBrackets:
74
74
  Style/AndOr:
75
75
  Enabled: false
76
76
 
77
- Style/TrailingComma:
77
+ Style/TrailingCommaInLiteral:
78
78
  Enabled: true
79
79
 
80
80
  Style/SpaceBeforeComma:
@@ -98,7 +98,7 @@ Style/SpaceAfterColon:
98
98
  Style/SpaceAfterComma:
99
99
  Enabled: true
100
100
 
101
- Style/SpaceAfterControlKeyword:
101
+ Style/SpaceAroundKeyword:
102
102
  Enabled: true
103
103
 
104
104
  Style/SpaceAfterNot:
@@ -163,7 +163,7 @@ Style/StringLiterals:
163
163
  EnforcedStyle: double_quotes
164
164
 
165
165
  Metrics/CyclomaticComplexity:
166
- Max: 8
166
+ Max: 10
167
167
 
168
168
  Metrics/LineLength:
169
169
  Max: 128
@@ -214,3 +214,6 @@ Metrics/ParameterLists:
214
214
 
215
215
  Metrics/PerceivedComplexity:
216
216
  Enabled: false
217
+
218
+ Style/Documentation:
219
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.0
3
+ - 2.3.0
4
4
  services:
5
5
  - redis-server
data/CHANGELOG.md CHANGED
@@ -1,7 +1,17 @@
1
+ ## 0.1.6
2
+
3
+ - Add Zhong.any_running? for monitoring that any Zhong node has checked in rencently
4
+ - More code cleanup/refactoring.
5
+
1
6
  ## 0.1.5
2
7
 
3
8
  - Improve the API.
4
9
  - Add scheduler test.
10
+ - Add Zhong::Web to see job status, and enable/disable jobs. Activate it with:
11
+ Rails.application.routes.draw do
12
+ # ...
13
+ require "zhong/web"
14
+ mount Zhong::Web => "/zhong" # or wherever
5
15
 
6
16
  ## 0.1.4
7
17
 
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in zhong.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -45,20 +45,21 @@ Zhong.schedule do
45
45
  true
46
46
  end
47
47
 
48
+ on(:before_run) do |job, time|
49
+ puts "running #{job}"
50
+ true # can conditionally run a specific job
51
+ end
52
+
53
+ on(:after_run) do |job, time, ran|
54
+ puts "#{job} ran?: #{ran}"
55
+ end
56
+
48
57
  error_handler do |e, job|
49
- puts "damn, #{job} messed up: #{e}"
58
+ puts "dang, #{job} messed up: #{e}"
50
59
  end
51
60
  end
52
61
  ```
53
62
 
54
- ## TODO
55
- - better logging
56
- - error handling
57
- - tests
58
- - examples
59
- - callbacks
60
- - generic handler
61
-
62
63
  ## History
63
64
 
64
65
  View the [changelog](https://github.com/nickelser/zhong/blob/master/CHANGELOG.md).
data/Rakefile CHANGED
@@ -1,8 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- task default: :test
5
- Rake::TestTask.new do |t|
4
+ Rake::TestTask.new(:test) do |t|
6
5
  t.libs << "test"
7
- t.pattern = "test/**/*_test.rb"
6
+ t.pattern = "test/**/test_*.rb"
8
7
  end
8
+
9
+ task default: :test
data/lib/zhong/at.rb CHANGED
@@ -24,7 +24,7 @@ module Zhong
24
24
  parse_at(at, grace)
25
25
  end
26
26
  rescue ArgumentError
27
- fail FailedToParse, at
27
+ raise FailedToParse, at
28
28
  end
29
29
 
30
30
  def self.deserialize(at)
@@ -37,7 +37,7 @@ module Zhong
37
37
  @wday = wday
38
38
  @grace = grace
39
39
 
40
- fail ArgumentError unless valid?
40
+ raise ArgumentError unless valid?
41
41
  end
42
42
 
43
43
  def next_at(time = Time.now)
@@ -46,11 +46,11 @@ module Zhong
46
46
  grace_cutoff = time.change(sec: 0) - @grace
47
47
 
48
48
  if at_time < grace_cutoff
49
- if @wday.nil?
50
- at_time += @hour.nil? ? 1.hour : 1.day
51
- else
52
- at_time += 1.week
53
- end
49
+ at_time + if @wday.nil?
50
+ @hour.nil? ? 1.hour : 1.day
51
+ else
52
+ 1.week
53
+ end
54
54
  else
55
55
  at_time
56
56
  end
@@ -58,10 +58,7 @@ module Zhong
58
58
 
59
59
  def to_s
60
60
  str = "#{formatted_time(@hour)}:#{formatted_time(@minute)}"
61
-
62
- if @wday
63
- str += " on #{WDAYS.invert[@wday].capitalize}"
64
- end
61
+ str += " on #{WDAYS.invert[@wday].capitalize}" if @wday
65
62
 
66
63
  str
67
64
  end
@@ -74,57 +71,59 @@ module Zhong
74
71
  MessagePack.pack(as_json)
75
72
  end
76
73
 
77
- protected
78
-
79
- def formatted_time(t)
80
- if t.nil?
81
- "**"
74
+ def self.parse_serialized(at)
75
+ if at.is_a?(Array)
76
+ MultiAt.new(at.map { |a| parse_serialized(a) })
82
77
  else
83
- t.to_s.rjust(2, "0")
78
+ new(minute: at["m"], hour: at["h"], wday: at["w"], grace: at["g"])
84
79
  end
85
80
  end
86
-
87
- def ==(o)
88
- o.class == self.class && o.state == state
89
- end
90
-
91
- def state
92
- [@minute, @hour, @wday]
93
- end
94
-
95
- private
81
+ private_class_method :parse_serialized
96
82
 
97
83
  def self.parse_at(at, grace)
98
84
  case at
99
85
  when /\A([[:alpha:]]+)\s+(.*)\z/
100
86
  wday = WDAYS[$1.downcase]
101
87
 
102
- if wday
103
- parsed_time = parse_at($2, grace)
104
- parsed_time.wday = wday
105
- parsed_time
106
- else
107
- fail FailedToParse, at
108
- end
88
+ raise FailedToParse, at unless wday
89
+
90
+ parsed_time = parse_at($2, grace)
91
+ parsed_time.wday = wday
92
+ parsed_time
109
93
  when /\A(\d{1,2}):(\d\d)\z/
110
94
  new(minute: $2.to_i, hour: $1.to_i, grace: grace)
111
95
  when /\A\*{1,2}:(\d\d)\z/
112
96
  new(minute: $1.to_i, grace: grace)
113
97
  when /\A(\d{1,2}):\*{1,2}\z/
114
98
  new(hour: $1.to_i, grace: grace)
99
+ when /\A\*{1,2}:\*{1,2}\z/
100
+ new(grace: grace)
115
101
  else
116
- fail FailedToParse, at
102
+ raise FailedToParse, at
117
103
  end
118
104
  end
105
+ private_class_method :parse_at
119
106
 
120
- def self.parse_serialized(at)
121
- if at.is_a?(Array)
122
- MultiAt.new(at.map { |a| parse_serialized(a) })
107
+ protected
108
+
109
+ def formatted_time(t)
110
+ if t.nil?
111
+ "**"
123
112
  else
124
- new(minute: at["m"], hour: at["h"], wday: at["w"], grace: at["g"])
113
+ t.to_s.rjust(2, "0")
125
114
  end
126
115
  end
127
116
 
117
+ def ==(other)
118
+ other.class == self.class && other.state == state
119
+ end
120
+
121
+ def state
122
+ [@minute, @hour, @wday]
123
+ end
124
+
125
+ private
126
+
128
127
  def at_time_hour_minute_adjusted(time)
129
128
  if @minute && @hour
130
129
  time.change(hour: @hour, min: @minute)
@@ -155,8 +154,8 @@ module Zhong
155
154
  @ats = ats
156
155
  end
157
156
 
158
- def ==(o)
159
- o.class == self.class && @ats == o.ats
157
+ def ==(other)
158
+ other.class == self.class && @ats == other.ats
160
159
  end
161
160
 
162
161
  def next_at(time = Time.now)
data/lib/zhong/every.rb CHANGED
@@ -15,20 +15,17 @@ module Zhong
15
15
  def initialize(period)
16
16
  @period = period
17
17
 
18
- fail "`every` must be >= 1 second" unless valid?
18
+ raise "`every` must be >= 1 second" unless valid?
19
19
  end
20
20
 
21
21
  def to_s
22
22
  EVERY_KEYWORDS.to_a.reverse.each do |friendly, period|
23
- if @period % period == 0
24
- rem = @period / period
25
-
26
- if rem == 1
27
- return "#{rem} #{friendly}"
28
- else
29
- return "#{rem} #{friendly}s"
30
- end
31
- end
23
+ next unless @period % period == 0
24
+
25
+ rem = @period / period
26
+
27
+ return "#{rem} #{friendly}" if rem == 1
28
+ return "#{rem} #{friendly}s"
32
29
  end
33
30
 
34
31
  "#{@period.to_i} second#{@period.to_i == 1 ? '' : 's'}"
@@ -49,11 +46,11 @@ module Zhong
49
46
  when String, Symbol
50
47
  key = every.downcase.to_sym
51
48
 
52
- fail FailedToParse, every unless EVERY_KEYWORDS.key?(key)
49
+ raise FailedToParse, every unless EVERY_KEYWORDS.key?(key)
53
50
 
54
51
  new(EVERY_KEYWORDS[key])
55
52
  else
56
- fail FailedToParse, every
53
+ raise FailedToParse, every
57
54
  end
58
55
  end
59
56
  end
data/lib/zhong/job.rb CHANGED
@@ -11,7 +11,7 @@ module Zhong
11
11
  @at = config[:at] ? At.parse(config[:at], grace: config.fetch(:grace, 15.minutes)) : nil
12
12
  @every = config[:every] ? Every.parse(config[:every]) : nil
13
13
 
14
- fail "must specific either `at` or `every` for job: #{self}" unless @at || @every
14
+ raise "must specific either `at` or `every` for job: #{self}" unless @at || @every
15
15
 
16
16
  @block = block
17
17
 
@@ -39,6 +39,7 @@ module Zhong
39
39
 
40
40
  locked = false
41
41
  errored = false
42
+ ran = false
42
43
 
43
44
  begin
44
45
  redis_lock.lock do
@@ -61,6 +62,7 @@ module Zhong
61
62
  if @block
62
63
  begin
63
64
  @block.call
65
+ ran = true
64
66
  rescue => boom
65
67
  logger.error "#{self} failed: #{boom}"
66
68
  error_handler.call(boom, self) if error_handler
@@ -77,6 +79,8 @@ module Zhong
77
79
  @running = false
78
80
 
79
81
  logger.info "unable to acquire exclusive run lock: #{self}" if !locked && !errored
82
+
83
+ ran
80
84
  end
81
85
 
82
86
  def running?
@@ -97,7 +101,7 @@ module Zhong
97
101
  end
98
102
 
99
103
  def disabled?
100
- !!@redis.get(disabled_key)
104
+ !@redis.get(disabled_key).nil?
101
105
  end
102
106
 
103
107
  def to_s
@@ -19,10 +19,19 @@ module Zhong
19
19
  @tz = @config[:tz]
20
20
  @category = nil
21
21
  @error_handler = nil
22
+ @running = false
23
+ end
24
+
25
+ def clear
26
+ raise "unable to clear while running; run Zhong.stop first" if @running
27
+
28
+ @jobs = {}
29
+ @callbacks = {}
30
+ @category = nil
22
31
  end
23
32
 
24
33
  def category(name)
25
- fail "cannot nest categories: #{name} would be nested in #{@category} (#{caller.first})" if @category
34
+ raise "cannot nest categories: #{name} would be nested in #{@category} (#{caller.first})" if @category
26
35
 
27
36
  @category = name.to_s
28
37
 
@@ -32,7 +41,7 @@ module Zhong
32
41
  end
33
42
 
34
43
  def every(period, name, opts = {}, &block)
35
- fail "must specify a period for #{name} (#{caller.first})" unless period
44
+ raise "must specify a period for #{name} (#{caller.first})" unless period
36
45
 
37
46
  job = Job.new(name, opts.merge(@config).merge(every: period, category: @category), &block)
38
47
 
@@ -50,7 +59,7 @@ module Zhong
50
59
  end
51
60
 
52
61
  def on(event, &block)
53
- fail "unknown callback #{event}" unless [:before_tick, :after_tick, :before_run, :after_run].include?(event.to_sym)
62
+ raise "unknown callback #{event}" unless [:before_tick, :after_tick, :before_run, :after_run].include?(event.to_sym)
54
63
  (@callbacks[event.to_sym] ||= []) << block
55
64
  end
56
65
 
@@ -61,7 +70,11 @@ module Zhong
61
70
 
62
71
  trap_signals
63
72
 
73
+ raise "already running" if @running
74
+
64
75
  loop do
76
+ @running = true
77
+
65
78
  if fire_callbacks(:before_tick)
66
79
  now = redis_time
67
80
 
@@ -83,14 +96,26 @@ module Zhong
83
96
  break if @stop
84
97
  end
85
98
 
99
+ @running = false
100
+
86
101
  Thread.new { @logger.info "stopped" }.join
87
102
  end
88
103
 
89
104
  def stop
90
- Thread.new { @logger.error "stopping" } # thread necessary due to trap context
105
+ Thread.new { @logger.error "stopping" } if @running # thread necessary due to trap context
91
106
  @stop = true
92
107
  end
93
108
 
109
+ def find_by_name(job_name)
110
+ @jobs[Digest::SHA256.hexdigest(job_name)]
111
+ end
112
+
113
+ def redis_time
114
+ s, ms = @redis.time # returns [seconds since epoch, microseconds]
115
+ now = Time.at(s + ms / (10**6))
116
+ @tz ? now.in_time_zone(@tz) : now
117
+ end
118
+
94
119
  private
95
120
 
96
121
  TRAPPED_SIGNALS = %w(QUIT INT TERM).freeze
@@ -107,9 +132,9 @@ module Zhong
107
132
  def run_job(job, time = redis_time)
108
133
  return unless fire_callbacks(:before_run, job, time)
109
134
 
110
- job.run(time, error_handler)
135
+ ran = job.run(time, error_handler)
111
136
 
112
- fire_callbacks(:after_run, job, time)
137
+ fire_callbacks(:after_run, job, time, ran)
113
138
  end
114
139
 
115
140
  def heartbeat(time)
@@ -130,11 +155,5 @@ module Zhong
130
155
  GC.start
131
156
  sleep(1.0 - Time.now.subsec + 0.0001)
132
157
  end
133
-
134
- def redis_time
135
- s, ms = @redis.time # returns [seconds since epoch, microseconds]
136
- now = Time.at(s + ms / (10**6))
137
- @tz ? now.in_time_zone(@tz) : now
138
- end
139
158
  end
140
159
  end
data/lib/zhong/util.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Zhong
2
+ module Util
3
+ def safe_mget(keys)
4
+ if keys.empty?
5
+ {}
6
+ else
7
+ Zhong.redis.mapped_mget(*keys)
8
+ end
9
+ end
10
+
11
+ module_function :safe_mget
12
+ end
13
+ end
data/lib/zhong/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Zhong
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6".freeze
3
3
  end
data/lib/zhong/web.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "tilt/erubis"
3
2
  require "erb"
4
3
  require "sinatra/base"
5
4
 
@@ -29,21 +28,21 @@ module Zhong
29
28
 
30
29
  helpers WebHelpers
31
30
 
32
- get '/' do
31
+ get "/" do
33
32
  index
34
33
 
35
34
  erb :index
36
35
  end
37
36
 
38
- post '/' do
39
- if params['disable']
40
- if job = Zhong.jobs[params['disable']]
41
- job.disable
42
- end
43
- elsif params['enable']
44
- if job = Zhong.jobs[params['enable']]
45
- job.enable
46
- end
37
+ post "/" do
38
+ if params["disable"]
39
+ job = Zhong.jobs[params["disable"]]
40
+
41
+ job.disable if job
42
+ elsif params["enable"]
43
+ job = Zhong.jobs[params["enable"]]
44
+
45
+ job.enable if job
47
46
  end
48
47
 
49
48
  index
@@ -55,30 +54,18 @@ module Zhong
55
54
  @jobs = Zhong.jobs.values
56
55
  @last_runs = zhong_mget(@jobs, "last_ran")
57
56
  @disabled = zhong_mget(@jobs, "disabled")
58
- @hosts = safe_mget(Zhong.redis.scan_each(match: "zhong:heartbeat:*").to_a).map do |k, v|
59
- host, pid = k.split("zhong:heartbeat:", 2)[1].split("#", 2)
60
- {host: host, pid: pid, last_seen: Time.at(v.to_i)}
61
- end
57
+ @hosts = Zhong.all_heartbeats
62
58
  end
63
59
 
64
60
  def zhong_mget(jobs, key)
65
61
  keys = jobs.map(&:to_s)
66
- ret = safe_mget(keys.map { |j| "zhong:#{key}:#{j}" })
62
+ ret = Zhong::Util.safe_mget(keys.map { |j| "zhong:#{key}:#{j}" })
67
63
  Hash[keys.map { |j| [j, ret["zhong:#{key}:#{j}"]] }]
68
64
  end
69
-
70
- def safe_mget(keys)
71
- if keys.length > 0
72
- Zhong.redis.mapped_mget(*keys)
73
- else
74
- {}
75
- end
76
- end
77
65
  end
78
66
  end
79
67
 
80
- if defined?(::ActionDispatch::Request::Session) &&
81
- !::ActionDispatch::Request::Session.respond_to?(:each)
68
+ if defined?(::ActionDispatch::Request::Session) && !::ActionDispatch::Request::Session.respond_to?(:each)
82
69
  # mperham/sidekiq#2460
83
70
  # Rack apps can't reuse the Rails session store without
84
71
  # this monkeypatch
@@ -7,7 +7,7 @@ module Zhong
7
7
  # capture method from sinatra-contrib library.
8
8
  def capture(&block)
9
9
  block.call
10
- eval('', block.binding)
10
+ eval("", block.binding)
11
11
  end
12
12
 
13
13
  def root_path
@@ -15,12 +15,12 @@ module Zhong
15
15
  end
16
16
 
17
17
  def current_path
18
- @current_path ||= request.path_info.gsub(/^\//,'')
18
+ @current_path ||= request.path_info.gsub(%r(^\/),"")
19
19
  end
20
20
 
21
21
  def relative_time(time)
22
22
  if time
23
- %{<time datetime="#{time.getutc.iso8601}">#{time}</time>}
23
+ %(<time datetime="#{time.getutc.iso8601}">#{time}</time>)
24
24
  else
25
25
  "never"
26
26
  end
@@ -41,14 +41,12 @@ module Zhong
41
41
  end
42
42
 
43
43
  def to_display(arg)
44
+ arg.inspect
45
+ rescue
44
46
  begin
45
- arg.inspect
46
- rescue
47
- begin
48
- arg.to_s
49
- rescue => ex
50
- "Cannot display argument: [#{ex.class.name}] #{ex.message}"
51
- end
47
+ arg.to_s
48
+ rescue => ex
49
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
52
50
  end
53
51
  end
54
52
 
@@ -59,8 +57,8 @@ module Zhong
59
57
  return number
60
58
  end
61
59
 
62
- options = {delimiter: ',', separator: '.'}
63
- parts = number.to_s.to_str.split('.')
60
+ options = {delimiter: ",", separator: "."}
61
+ parts = number.to_s.to_str.split(".")
64
62
  parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
65
63
  parts.join(options[:separator])
66
64
  end
@@ -68,13 +66,13 @@ module Zhong
68
66
  def h(text)
69
67
  ::Rack::Utils.escape_html(text)
70
68
  rescue ArgumentError => e
71
- raise unless e.message.eql?('invalid byte sequence in UTF-8')
72
- text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16')
69
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
70
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
73
71
  retry
74
72
  end
75
73
 
76
74
  def environment_title_prefix
77
- environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
75
+ environment = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
78
76
 
79
77
  "[#{environment.upcase}] " unless environment == "production"
80
78
  end
data/test/helper.rb ADDED
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ if ENV["CODECLIMATE_REPO_TOKEN"]
4
+ require "codeclimate-test-reporter"
5
+ ::SimpleCov.add_filter "helper"
6
+ CodeClimate::TestReporter.start
7
+ end
8
+
9
+ require "zhong"
10
+ require "minitest/autorun"
11
+ require "rack/test"
12
+
13
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = "test"
14
+
15
+ def assert_contains(expected_substring, string, *args)
16
+ assert string.include?(expected_substring), *args
17
+ end
18
+
19
+ Zhong.logger = begin
20
+ l = Logger.new(STDOUT)
21
+ l.level = Logger::ERROR
22
+ l
23
+ end
24
+
25
+ Zhong.redis = Redis.new(url: ENV["REDIS_URL"] || "redis://localhost/13")
26
+
27
+ def test_default_config
28
+ @default_config ||= {
29
+ redis: Zhong.redis,
30
+ logger: Zhong.logger,
31
+ long_running_timeout: 10.seconds
32
+ }
33
+ end
@@ -1,4 +1,4 @@
1
- require "test_helper"
1
+ require_relative "helper"
2
2
 
3
3
  # Many At tests lifted from Clockwork (thanks Clockwork!)
4
4
  class TestAt < Minitest::Test
@@ -139,6 +139,32 @@ class TestAt < Minitest::Test
139
139
  assert_equal time_in_day(8, 20, 3), at.next_at(time_in_day(12, 31, 2))
140
140
  end
141
141
 
142
+ def test_to_s
143
+ at = Zhong::At.parse("thr 12:59")
144
+
145
+ assert_equal "12:59 on Thr", at.to_s
146
+
147
+ at = Zhong::At.parse(["8:20", "tues 12:30"])
148
+
149
+ assert_equal "08:20, 12:30 on Tues", at.to_s
150
+ end
151
+
152
+ def test_as_json
153
+ at = Zhong::At.parse("tues 23:01")
154
+
155
+ assert_equal({m: 1, h: 23, w: 2, g: 0.seconds}, at.as_json)
156
+
157
+ at = Zhong::At.parse(["8:**", "sun 12:30"])
158
+
159
+ assert_equal([{m: nil, h: 8, w: nil, g: 0.seconds}, {m: 30, h: 12, w: 0, g: 0.seconds}], at.as_json)
160
+ end
161
+
162
+ def test_serialize
163
+ at = Zhong::At.parse(["8:20", "tues 12:30"])
164
+
165
+ assert_equal Zhong::At.deserialize(at.serialize), at
166
+ end
167
+
142
168
  def test_invalid_time_32
143
169
  assert_raises Zhong::At::FailedToParse do
144
170
  Zhong::At.parse("32:00")
@@ -1,4 +1,4 @@
1
- require "test_helper"
1
+ require_relative "helper"
2
2
 
3
3
  class TestEvery < Minitest::Test
4
4
  def time_in_day(hour, minute, day = 0, sec = 0)
data/test/test_job.rb ADDED
@@ -0,0 +1,74 @@
1
+ require_relative "helper"
2
+
3
+ class TestJob < Minitest::Test
4
+ def test_initialize
5
+ job = Zhong::Job.new("test_initialize", {at: "12:00"}.merge(test_default_config))
6
+
7
+ assert job
8
+ assert !job.running?
9
+ assert_equal "test_initialize", job.to_s
10
+ end
11
+
12
+ def test_should_run
13
+ sleep 1
14
+
15
+ job = Zhong::Job.new("test_should_run", {every: 1.second}.merge(test_default_config))
16
+
17
+ assert_equal true, job.run?
18
+ end
19
+
20
+ def test_run
21
+ success_counter = Queue.new
22
+ job = Zhong::Job.new("test_run", {every: 1.second}.merge(test_default_config)) { success_counter << 1 }
23
+ now = Time.now
24
+
25
+ assert_equal 0, success_counter.size
26
+ assert_equal true, job.run?(now)
27
+ job.run(now)
28
+ assert_equal false, job.run?(now)
29
+ assert_equal 1, success_counter.size
30
+ end
31
+
32
+ def test_run_at
33
+ success_counter = Queue.new
34
+ job = Zhong::Job.new("test_run_at", {every: 1.second, at: ["**:**"]}.merge(test_default_config)) { success_counter << 1 }
35
+ now = Time.now
36
+
37
+ assert_equal 0, success_counter.size
38
+ assert_equal true, job.run?(now)
39
+ job.run(now)
40
+ assert_equal false, job.run?(now)
41
+ assert_equal 1, success_counter.size
42
+ end
43
+
44
+ def test_run_at_change
45
+ success_counter = Queue.new
46
+ job = Zhong::Job.new("test_run_at_change", {every: 1.second, at: ["**:**"]}.merge(test_default_config)) { success_counter << 1 }
47
+ now = Time.now
48
+
49
+ assert_equal 0, success_counter.size
50
+ assert_equal true, job.run?(now)
51
+ job.run(now)
52
+ assert_equal false, job.run?(now)
53
+ assert_equal 1, success_counter.size
54
+
55
+ job = Zhong::Job.new("test_run_at_change", {every: 1.second, at: ["**:**", "**:**"]}.merge(test_default_config)) { success_counter << 1 }
56
+ assert_equal true, job.run?(now)
57
+ job.run(now)
58
+ assert_equal false, job.run?(now)
59
+ assert_equal 2, success_counter.size
60
+ end
61
+
62
+ def test_disable
63
+ success_counter = Queue.new
64
+ job = Zhong::Job.new("test_disable", {every: 1.second}.merge(test_default_config)) { success_counter << 1 }
65
+ now = Time.now
66
+
67
+ job.disable
68
+
69
+ assert_equal true, job.run?(now)
70
+ job.run(now)
71
+ assert_equal true, job.run?(now)
72
+ assert_equal 0, success_counter.size
73
+ end
74
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "helper"
2
+
3
+ class TestLibrary < Minitest::Test
4
+ def test_that_it_has_a_version_number
5
+ refute_nil ::Zhong::VERSION
6
+ end
7
+
8
+ def teardown
9
+ Zhong.stop
10
+ sleep 1
11
+ end
12
+
13
+ def test_heartbeats
14
+ Zhong.schedule { nil }
15
+ t = Thread.new { Zhong.start }
16
+ sleep(1)
17
+ assert_equal true, Zhong.any_running?
18
+ assert_in_delta Zhong.redis_time.to_f, Time.now.to_f, 0.1
19
+ assert_in_delta Zhong.redis_time.to_f, Zhong.latest_heartbeat.to_f, 1
20
+ Zhong.stop
21
+ t.join
22
+ end
23
+ end
@@ -0,0 +1,80 @@
1
+ require_relative "helper"
2
+
3
+ class TestScheduler < Minitest::Test
4
+ def setup
5
+ Zhong.clear
6
+ end
7
+
8
+ def test_scheduler
9
+ test_one_counter = 0
10
+ test_two_counter = 0
11
+
12
+ Zhong.schedule do
13
+ every(10.seconds, "test_one") { test_one_counter += 1 }
14
+ every(3.seconds, "test_two") { test_two_counter += 1 }
15
+ end
16
+
17
+ t = Thread.new { Zhong.start }
18
+ sleep(7)
19
+ Zhong.stop
20
+ t.join
21
+
22
+ assert_equal 1, test_one_counter
23
+ assert_equal 3, test_two_counter
24
+ end
25
+
26
+ def test_scheduler_categories
27
+ Zhong.schedule do
28
+ category "cat1" do
29
+ every(10.seconds, "cat_test_one") { nil }
30
+ end
31
+ end
32
+
33
+ assert_equal 1, Zhong.jobs.size
34
+ assert_equal "cat1.cat_test_one", Zhong.jobs.values.first.to_s
35
+ end
36
+
37
+ def test_scheduler_nested_category
38
+ assert_raises RuntimeError do
39
+ Zhong.schedule do
40
+ category "cat1" do
41
+ category "cat2"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def test_scheduler_callbacks
48
+ test_before_tick = 0
49
+ test_after_tick = 0
50
+ test_errors = 0
51
+ test_before_run = 0
52
+ test_after_run = 0
53
+
54
+ Zhong.schedule do
55
+ on(:before_tick) { test_before_tick += 1; true }
56
+ on(:after_tick) { test_after_tick += 1 }
57
+ on(:before_run) { test_before_run += 1; true }
58
+ on(:after_run) { |_, _, ran| test_after_run += 1 if ran }
59
+ error_handler { test_errors += 1 }
60
+
61
+ every(1.second, "test_every_second") { nil }
62
+ every(1.second, "break_every_second") { raise "boom" }
63
+ end
64
+
65
+ t = Thread.new { Zhong.start }
66
+ sleep(3)
67
+ Zhong.stop
68
+ t.join
69
+
70
+ assert_operator 2, :<=, test_before_tick
71
+ assert_operator 4, :>=, test_before_tick
72
+ assert_operator 2, :<=, test_after_tick
73
+ assert_operator 4, :>=, test_after_tick
74
+ assert_operator 6, :<=, test_before_run
75
+ assert_operator 8, :>=, test_before_run
76
+ assert_operator 2, :<=, test_after_run
77
+ assert_operator 4, :>=, test_after_run
78
+ assert_operator 2, :<=, test_errors
79
+ end
80
+ end
data/test/test_web.rb ADDED
@@ -0,0 +1,86 @@
1
+ require_relative "helper"
2
+ require "zhong/web"
3
+ require "tilt/erubis"
4
+
5
+ class TestWeb < Minitest::Test
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ Zhong::Web
10
+ end
11
+
12
+ def setup
13
+ Zhong.clear
14
+ end
15
+
16
+ def test_index
17
+ get "/"
18
+ assert last_response.ok?
19
+ assert_contains "<title>[TEST] Zhong</title>", last_response.body
20
+ assert_contains Zhong::VERSION, last_response.body
21
+ end
22
+
23
+ def test_index_job
24
+ Zhong.schedule do
25
+ every(10.minutes, "test_web_job") { nil }
26
+ end
27
+
28
+ get "/"
29
+ assert last_response.ok?
30
+ assert_contains "test_web_job", last_response.body
31
+ assert_contains "every 10 minutes", last_response.body
32
+ end
33
+
34
+ def test_disable_job
35
+ Zhong.schedule do
36
+ every(30.seconds, "test_disable_web_job") { nil }
37
+ end
38
+
39
+ job = Zhong.scheduler.find_by_name("test_disable_web_job")
40
+
41
+ job.enable
42
+
43
+ assert_equal false, job.disabled?
44
+
45
+ post "/", "disable" => job.id
46
+ assert last_response.ok?
47
+ assert_contains "test_disable_web_job", last_response.body
48
+ assert_contains "every 30 seconds", last_response.body
49
+ assert_contains 'name="enable"', last_response.body
50
+ assert_equal true, job.disabled?
51
+ end
52
+
53
+ def test_enable_job
54
+ Zhong.schedule do
55
+ every(12.hours, "test_enable_web_job") { nil }
56
+ end
57
+
58
+ job = Zhong.scheduler.find_by_name("test_enable_web_job")
59
+
60
+ job.disable
61
+
62
+ assert_equal true, job.disabled?
63
+
64
+ post "/", "enable" => job.id
65
+ assert last_response.ok?
66
+ assert_contains "test_enable_web_job", last_response.body
67
+ assert_contains "every 12 hours", last_response.body
68
+ assert_contains 'name="disable"', last_response.body
69
+ assert_equal false, job.disabled?
70
+ end
71
+
72
+ def test_heartbeat
73
+ hostname = `hostname`.strip
74
+ pid = Process.pid
75
+
76
+ t = Thread.new { Zhong.start }
77
+ sleep(1)
78
+ Zhong.stop
79
+ t.join
80
+
81
+ get "/"
82
+ assert last_response.ok?
83
+ assert_contains hostname, last_response.body
84
+ assert_contains "PID #{pid}", last_response.body
85
+ end
86
+ end
data/zhong.gemspec CHANGED
@@ -32,4 +32,8 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency "rubocop", "~> 0.30.0"
33
33
  spec.add_development_dependency "minitest", "~> 5.5.0"
34
34
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4.7"
35
+ spec.add_development_dependency "sinatra", "~> 1.4", ">= 1.4.6"
36
+ spec.add_development_dependency "rack-test", "~> 0.6"
37
+ spec.add_development_dependency "tilt"
38
+ spec.add_development_dependency "erubis"
35
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zhong
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Elser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-28 00:00:00.000000000 Z
11
+ date: 2016-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: suo
@@ -136,6 +136,68 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.4.7
139
+ - !ruby/object:Gem::Dependency
140
+ name: sinatra
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.4'
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 1.4.6
149
+ type: :development
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '1.4'
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 1.4.6
159
+ - !ruby/object:Gem::Dependency
160
+ name: rack-test
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.6'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '0.6'
173
+ - !ruby/object:Gem::Dependency
174
+ name: tilt
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: erubis
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
139
201
  description: Reliable, distributed cron.
140
202
  email:
141
203
  - nick.elser@gmail.com
@@ -165,15 +227,17 @@ files:
165
227
  - lib/zhong/every.rb
166
228
  - lib/zhong/job.rb
167
229
  - lib/zhong/scheduler.rb
230
+ - lib/zhong/util.rb
168
231
  - lib/zhong/version.rb
169
232
  - lib/zhong/web.rb
170
233
  - lib/zhong/web_helpers.rb
171
- - test/at_test.rb
172
- - test/every_test.rb
173
- - test/job_test.rb
174
- - test/library_test.rb
175
- - test/scheduler_test.rb
176
- - test/test_helper.rb
234
+ - test/helper.rb
235
+ - test/test_at.rb
236
+ - test/test_every.rb
237
+ - test/test_job.rb
238
+ - test/test_library.rb
239
+ - test/test_scheduler.rb
240
+ - test/test_web.rb
177
241
  - web/assets/javascript/application.js
178
242
  - web/assets/javascript/vendor.min.js
179
243
  - web/views/index.erb
@@ -203,10 +267,11 @@ signing_key:
203
267
  specification_version: 4
204
268
  summary: Reliable, distributed cron.
205
269
  test_files:
206
- - test/at_test.rb
207
- - test/every_test.rb
208
- - test/job_test.rb
209
- - test/library_test.rb
210
- - test/scheduler_test.rb
211
- - test/test_helper.rb
270
+ - test/helper.rb
271
+ - test/test_at.rb
272
+ - test/test_every.rb
273
+ - test/test_job.rb
274
+ - test/test_library.rb
275
+ - test/test_scheduler.rb
276
+ - test/test_web.rb
212
277
  has_rdoc:
data/test/job_test.rb DELETED
@@ -1,60 +0,0 @@
1
- require "test_helper"
2
-
3
- class TestJob < Minitest::Test
4
- def default_config
5
- @default_config ||= {
6
- redis: Redis.new,
7
- logger: logger,
8
- long_running_timeout: 5.minutes
9
- }
10
- end
11
-
12
- def logger
13
- @logger ||= begin
14
- l = Logger.new(STDOUT)
15
- l.level = Logger::UNKNOWN
16
- l
17
- end
18
- end
19
-
20
- def test_initialize
21
- job = Zhong::Job.new("test_initialize", {at: "12:00"}.merge(default_config))
22
-
23
- assert job
24
- assert !job.running?
25
- assert_equal "test_initialize", job.to_s
26
- end
27
-
28
- def test_should_run
29
- sleep 1
30
-
31
- job = Zhong::Job.new("test_should_run", {every: 1.second}.merge(default_config))
32
-
33
- assert_equal true, job.run?
34
- end
35
-
36
- def test_run
37
- success_counter = Queue.new
38
- job = Zhong::Job.new("test_run", {every: 1.second}.merge(default_config)) { success_counter << 1 }
39
- now = Time.now
40
-
41
- assert_equal 0, success_counter.size
42
- assert_equal true, job.run?(now)
43
- job.run(now)
44
- assert_equal false, job.run?(now)
45
- assert_equal 1, success_counter.size
46
- end
47
-
48
- def test_disable
49
- success_counter = Queue.new
50
- job = Zhong::Job.new("test_disable", {every: 1.second}.merge(default_config)) { success_counter << 1 }
51
- now = Time.now
52
-
53
- job.disable
54
-
55
- assert_equal true, job.run?(now)
56
- job.run(now)
57
- assert_equal true, job.run?(now)
58
- assert_equal 0, success_counter.size
59
- end
60
- end
data/test/library_test.rb DELETED
@@ -1,7 +0,0 @@
1
- require "test_helper"
2
-
3
- class TestLibrary < Minitest::Test
4
- def test_that_it_has_a_version_number
5
- refute_nil ::Zhong::VERSION
6
- end
7
- end
@@ -1,30 +0,0 @@
1
- require "test_helper"
2
-
3
- class TestScheduler < Minitest::Test
4
- def logger
5
- @logger ||= begin
6
- l = Logger.new(STDOUT)
7
- l.level = Logger::UNKNOWN
8
- l
9
- end
10
- end
11
-
12
- def test_scheduler
13
- test_one_counter = 0
14
- test_two_counter = 0
15
-
16
- Zhong.logger = logger
17
-
18
- Zhong.schedule do
19
- every(10.seconds, "test_one") { test_one_counter += 1 }
20
- every(3.seconds, "test_two") { test_two_counter += 1 }
21
- end
22
-
23
- t = Thread.new { Zhong.start }
24
- sleep(7)
25
- Zhong.stop
26
- t.join
27
- assert_equal 1, test_one_counter
28
- assert_equal 3, test_two_counter
29
- end
30
- end
data/test/test_helper.rb DELETED
@@ -1,11 +0,0 @@
1
- $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
-
3
- if ENV["CODECLIMATE_REPO_TOKEN"]
4
- require "codeclimate-test-reporter"
5
- CodeClimate::TestReporter.start
6
- end
7
-
8
- require "zhong"
9
- require "minitest/autorun"
10
-
11
- ENV["ZHONG_TEST"] = "true"