Compare commits

..

40 Commits

Author SHA1 Message Date
Dzmitry Plashchynski
d31aaaffbe v8.8.0.pre 2015-04-13 15:54:00 +03:00
Dzmitry Plashchynski
6d41a19212 Handle a few jobs scheduled at the same time 2015-04-13 15:53:20 +03:00
Dzmitry Plashchynski
a28ec7b276 Add specs for bug with jobs scheduled at same time without at 2015-04-13 14:19:34 +03:00
Dzmitry Plashchynski
e8c7400caa Do not freeze time for period specs 2015-04-13 14:18:55 +03:00
Dzmitry Plashchynski
260cf14e95 Fix Cli specs 2015-04-13 14:18:32 +03:00
Dzmitry Plashchynski
1900a06582 Add Crono::Cronotab to process cronotab 2015-04-13 12:43:52 +03:00
Dzmitry Plashchynski
8174f86407 Fix crono:clean task 2015-04-13 12:29:27 +03:00
Dzmitry Plashchynski
c8f9ff4e34 v0.8.7.pre 2015-04-12 17:04:43 +03:00
Dzmitry Plashchynski
84ac08e5d4 Fix error when next time in the past 2015-04-12 17:04:07 +03:00
Dzmitry Plashchynski
e8812b1329 v0.8.6.pre 2015-04-12 16:52:37 +03:00
Dzmitry Plashchynski
f6b393ad6b Period should only return future time 2015-04-12 16:48:57 +03:00
Dzmitry Plashchynski
55e3956618 Clean up 2015-04-12 16:34:28 +03:00
Dzmitry Plashchynski
50aec2ea87 0.8.5.pre 2015-04-11 18:50:02 +03:00
Dzmitry Plashchynski
aaa8bc40e5 Apply bundler gem template recommendations 2015-04-11 18:48:54 +03:00
Dzmitry Plashchynski
a11a8985c3 Merge branch 'master' of github.com:plashchynski/crono 2015-04-11 18:24:07 +03:00
Dzmitry Plashchynski
b4ad8fb953 Handle a few jobs scheduled at the same time 2015-04-08 20:08:58 +03:00
Dzmitry Plashchynski
b010b7349f Merge pull request #18 from michaelachrisco/view-multiple-states
Multiple View States
2015-04-04 19:07:21 +03:00
Dzmitry Plashchynski
cd5813049c Merge pull request #15 from michaelachrisco/rake_tasks
Readme Rake Tasks
2015-04-04 18:34:19 +03:00
MichaelAChrisco
c74291a001 multiple view states 2015-04-03 15:26:59 -07:00
MichaelAChrisco
4aeb29891e Rake Tasks 2015-04-03 14:11:26 -07:00
Dzmitry Plashchynski
4415211100 Merge pull request #13 from thomasfedb/daemon-pidfile-default
Default to not writing a pidfile unless daemonized
2015-04-02 17:59:19 +03:00
Thomas Drake-Brockman
4b28f3dd80 Updated CLI and Config to default to not writing a pidfile unless daemonized. 2015-04-02 03:14:08 +08:00
Dzmitry Plashchynski
48db3ef245 Merge pull request #12 from Trevoke/patch-1
Update README.md
2015-03-25 00:07:52 +02:00
Dzmitry Plashchynski
01a761d919 Merge pull request #11 from MiroslavCsonka/master
Update README.md
2015-03-25 00:05:37 +02:00
Aldric Giacomoni
75f1b43c84 Update README.md 2015-03-24 17:58:59 -04:00
Miroslav Csonka
13ab4838e7 Update README.md
Fix ruby typo
2015-03-24 22:51:56 +01:00
Dzmitry Plashchynski
d075a55f03 Merge branch 'master' of github.com:plashchynski/crono 2015-03-24 23:25:49 +02:00
Dzmitry Plashchynski
2d72020ac4 Fix spec 2015-03-24 23:25:38 +02:00
Dzmitry Plashchynski
cde8a2d214 Update README.md 2015-03-24 22:46:21 +02:00
Dzmitry Plashchynski
2ec9cfa829 Update Gemfile.lock 2015-03-24 22:24:08 +02:00
Dzmitry Plashchynski
63c86c8cd9 Bump version 2015-03-23 01:40:03 +02:00
Dzmitry Plashchynski
e10daec9c6 Fix typo 2015-03-23 01:38:53 +02:00
Dzmitry Plashchynski
f72c288ce8 Merge branch 'master' of github.com:plashchynski/crono 2015-03-22 23:54:25 +02:00
Dzmitry Plashchynski
78ce578484 Access to Crono::Job from performer class 2015-03-22 23:53:46 +02:00
Dzmitry Plashchynski
0c77c490bd Move TestJob to spec_helper.rb 2015-03-22 23:37:01 +02:00
Dzmitry Plashchynski
d889b9380d Update README.md 2015-03-20 22:45:18 +02:00
Dzmitry Plashchynski
f75bdf352b Update README.md 2015-03-19 21:12:40 +02:00
Dzmitry Plashchynski
fa97f573e0 Add rake task to check cronotab.rb syntax 2015-03-18 20:54:16 +02:00
Dzmitry Plashchynski
dc70212f9d Add crono rake tasks spec 2015-03-17 18:28:17 +02:00
Dzmitry Plashchynski
7328bea24c Add rake task to clean DB 2015-03-16 17:34:25 +02:00
35 changed files with 379 additions and 146 deletions

12
.gitignore vendored
View File

@@ -1,4 +1,8 @@
pkg/*
*.gem
.bundle
tmp/*.sqlite3
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

2
.rspec
View File

@@ -1,2 +1,2 @@
--color
--format documentation
--color

View File

@@ -1,2 +1,4 @@
source "https://rubygems.org"
source 'https://rubygems.org'
# Specify your gem's dependencies in crono.gemspec
gemspec

View File

@@ -1,7 +1,7 @@
PATH
remote: .
specs:
crono (0.8.0)
crono (0.8.8.pre)
activejob (~> 4.0)
activerecord (~> 4.0)
activesupport (~> 4.0)
@@ -9,17 +9,17 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activejob (4.2.0)
activesupport (= 4.2.0)
activejob (4.2.1)
activesupport (= 4.2.1)
globalid (>= 0.3.0)
activemodel (4.2.0)
activesupport (= 4.2.0)
activemodel (4.2.1)
activesupport (= 4.2.1)
builder (~> 3.1)
activerecord (4.2.0)
activemodel (= 4.2.0)
activesupport (= 4.2.0)
activerecord (4.2.1)
activemodel (= 4.2.1)
activesupport (= 4.2.1)
arel (~> 6.0)
activesupport (4.2.0)
activesupport (4.2.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -27,14 +27,11 @@ GEM
tzinfo (~> 1.1)
arel (6.0.0)
builder (3.2.2)
byebug (3.5.1)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
slop (~> 3.6)
byebug (4.0.5)
columnize (= 0.9.0)
columnize (0.9.0)
debugger-linecache (1.2.0)
diff-lcs (1.2.5)
globalid (0.3.3)
globalid (0.3.5)
activesupport (>= 4.1.0)
haml (4.0.6)
tilt
@@ -51,7 +48,7 @@ GEM
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.1)
rspec-core (3.2.2)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.0)
diff-lcs (>= 1.2.0, < 2.0)
@@ -64,7 +61,6 @@ GEM
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
slop (3.6.0)
sqlite3 (1.3.10)
thread_safe (0.3.5)
tilt (1.4.1)

View File

@@ -49,7 +49,7 @@ Now you are ready to move forward to create a job and schedule it.
Crono can use Active Job jobs from `app/jobs/`. The only requirements is that the `perform` method should take no arguments.
Here's an example of a test job:
Here's an example of a job:
```ruby
# app/jobs/test_job.rb
@@ -72,6 +72,35 @@ class TestJob # This is not an Active Job job, but pretty legal Crono job.
end
```
Here's an example of a Rake Task within a job:
```ruby
# config/cronotab.rb
require 'rake'
# Be sure to change AppName to your application name!
AppName::Application.load_tasks
class Test
def perform
Rake::Task['crono:hello'].invoke
end
end
Crono.perform(Test).every 5.seconds
```
With the rake task of:
```Ruby
# lib/tasks/test.rake
namespace :crono do
desc 'Update all tables'
task :hello => :environment do
puts "hello"
end
end
```
_Please note that crono uses threads, so your code should be thread-safe_
#### Job Schedule
Schedule list is defined in the file `config/cronotab.rb`, that created using `crono:install`. The semantic is pretty straightforward:
@@ -118,7 +147,7 @@ Crono comes with a Sinatra application that can display the current state of Cro
Add `sinatra` and `haml` to your Gemfile
```ruby
gam 'haml'
gem 'haml'
gem 'sinatra', require: nil
```

View File

@@ -1,5 +1,4 @@
require 'bundler'
Bundler::GemHelper.install_tasks
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new('spec')

14
bin/console Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "crono"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start

7
bin/setup Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
bundle install
# Do any other automated setup that you need to do here

View File

@@ -1,32 +1,34 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/crono/version', __FILE__)
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'crono/version'
Gem::Specification.new do |s|
s.name = 'crono'
s.version = Crono::VERSION
s.authors = ['Dzmitry Plashchynski']
s.email = ['plashchynski@gmail.com']
s.homepage = 'https://github.com/plashchynski/crono'
s.description = s.summary = 'Job scheduler for Rails'
s.license = 'Apache-2.0'
Gem::Specification.new do |spec|
spec.name = 'crono'
spec.version = Crono::VERSION
spec.authors = ['Dzmitry Plashchynski']
spec.email = ['plashchynski@gmail.com']
s.required_rubygems_version = '>= 1.3.6'
s.rubyforge_project = 'crono'
spec.summary = 'Job scheduler for Rails'
spec.description = 'A time-based background job scheduler daemon (just like Cron) for Rails'
spec.homepage = 'https://github.com/plashchynski/crono'
spec.license = 'Apache-2.0'
s.add_runtime_dependency 'activejob', '~> 4.0'
s.add_runtime_dependency 'activesupport', '~> 4.0'
s.add_runtime_dependency 'activerecord', '~> 4.0'
s.add_development_dependency 'rake', '~> 10.0'
s.add_development_dependency 'bundler', '>= 1.0.0'
s.add_development_dependency 'rspec', '~> 3.0'
s.add_development_dependency 'timecop', '~> 0.7'
s.add_development_dependency 'sqlite3'
s.add_development_dependency 'byebug'
s.add_development_dependency 'sinatra'
s.add_development_dependency 'haml'
s.add_development_dependency 'rack-test'
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = 'exe' # http://bundler.io/blog/2015/03/20/moving-bins-to-exe.html
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
s.files = `git ls-files`.split("\n")
s.executables = ['crono']
s.require_path = 'lib'
spec.add_runtime_dependency 'activejob', '~> 4.0'
spec.add_runtime_dependency 'activesupport', '~> 4.0'
spec.add_runtime_dependency 'activerecord', '~> 4.0'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'bundler', '>= 1.0.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'timecop', '~> 0.7'
spec.add_development_dependency 'sqlite3'
spec.add_development_dependency 'byebug'
spec.add_development_dependency 'sinatra'
spec.add_development_dependency 'haml'
spec.add_development_dependency 'rack-test'
end

View File

@@ -10,6 +10,8 @@ require 'crono/job'
require 'crono/scheduler'
require 'crono/config'
require 'crono/performer_proxy'
require 'crono/cronotab'
require 'crono/orm/active_record/crono_job'
require 'crono/railtie' if defined?(Rails)
Crono.autoload :Web, 'crono/web'

View File

@@ -2,8 +2,6 @@ require 'crono'
require 'optparse'
module Crono
mattr_accessor :scheduler
# Crono::CLI - The main class for the crono daemon exacutable `bin/crono`
class CLI
include Singleton
@@ -23,6 +21,7 @@ module Crono
write_pid
load_rails
Cronotab.process(File.expand_path(config.cronotab))
print_banner
check_jobs
@@ -33,7 +32,7 @@ module Crono
def setup_log
if config.daemonize
self.logifile = config.logfile
self.logfile = config.logfile
daemonize
else
self.logfile = STDOUT
@@ -52,6 +51,7 @@ module Crono
end
def write_pid
return unless config.pidfile
pidfile = File.expand_path(config.pidfile)
File.write(pidfile, ::Process.pid)
end
@@ -63,7 +63,7 @@ module Crono
logger.info 'Jobs:'
Crono.scheduler.jobs.each do |job|
logger.info "'#{job.performer}' with rule '#{job.period.description}'"\
"next time will perform at #{job.next}"
" next time will perform at #{job.next}"
end
end
@@ -72,7 +72,6 @@ module Crono
require 'rails'
require File.expand_path('config/environment.rb')
::Rails.application.eager_load!
require File.expand_path(config.cronotab)
end
def check_jobs
@@ -81,9 +80,10 @@ module Crono
end
def start_working_loop
while (job = Crono.scheduler.next)
sleep(job.next - Time.now)
job.perform
loop do
next_time, jobs = Crono.scheduler.next_jobs
sleep(next_time - Time.now) if next_time > Time.now
jobs.each(&:perform)
end
end

View File

@@ -10,9 +10,12 @@ module Crono
def initialize
self.cronotab = CRONOTAB
self.logfile = LOGFILE
self.pidfile = PIDFILE
self.daemonize = false
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
def pidfile
@pidfile || (daemonize ? PIDFILE : nil)
end
end
end

10
lib/crono/cronotab.rb Normal file
View File

@@ -0,0 +1,10 @@
module Crono
class Cronotab
def self.process(cronotab_path = nil)
cronotab_path ||= ENV['CRONOTAB'] || (defined?(Rails) &&
File.join(Rails.root, Config::CRONOTAB))
fail 'No cronotab defined' unless cronotab_path
require cronotab_path
end
end
end

View File

@@ -6,19 +6,20 @@ module Crono
class Job
include Logging
attr_accessor :performer, :period, :last_performed_at, :job_log,
:job_logger, :healthy
attr_accessor :performer, :period, :last_performed_at,
:next_performed_at, :job_log, :job_logger, :healthy
def initialize(performer, period)
self.performer, self.period = performer, period
self.job_log = StringIO.new
self.job_logger = Logger.new(job_log)
self.next_performed_at = period.next
@semaphore = Mutex.new
end
def next
next_time = period.next(since: last_performed_at)
next_time.past? ? period.next : next_time
return next_performed_at if next_performed_at.future?
Time.now
end
def description
@@ -32,6 +33,7 @@ module Crono
def perform
log "Perform #{performer}"
self.last_performed_at = Time.now
self.next_performed_at = period.next(since: last_performed_at)
Thread.new { perform_job }
end
@@ -45,6 +47,7 @@ module Crono
def load
self.last_performed_at = model.last_performed_at
self.next_performed_at = period.next(since: last_performed_at)
end
private
@@ -62,23 +65,24 @@ module Crono
def perform_job
performer.new.perform
finished_time_sec = format('%.2f', Time.now - last_performed_at)
rescue StandardError => e
handle_job_fail(e, finished_time_sec)
handle_job_fail(e)
else
handle_job_success(finished_time_sec)
handle_job_success
ensure
save
end
def handle_job_fail(exception, finished_time_sec)
def handle_job_fail(exception)
finished_time_sec = format('%.2f', Time.now - last_performed_at)
self.healthy = false
log_error "Finished #{performer} in #{finished_time_sec} seconds"\
"with error: #{exception.message}"
" with error: #{exception.message}"
log_error exception.backtrace.join("\n")
end
def handle_job_success(finished_time_sec)
def handle_job_success
finished_time_sec = format('%.2f', Time.now - last_performed_at)
self.healthy = true
log "Finished #{performer} in #{finished_time_sec} seconds"
end

View File

@@ -5,5 +5,9 @@ module Crono
class CronoJob < ActiveRecord::Base
self.table_name = 'crono_jobs'
validates :job_id, presence: true, uniqueness: true
def self.outdated
self
end
end
end

View File

@@ -1,5 +1,5 @@
module Crono
# Period describe frequency of performing a task
# Period describe frequency of jobs
class Period
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday,
:sunday]
@@ -14,7 +14,9 @@ module Crono
return initial_next unless since
@next = @period.since(since)
@next = @next.beginning_of_week.advance(days: @on) if @on
@next.change(time_atts)
@next = @next.change(time_atts)
return @next if @next.future?
Time.now
end
def description

9
lib/crono/railtie.rb Normal file
View File

@@ -0,0 +1,9 @@
module Crono
class Railtie < ::Rails::Railtie
rake_tasks do
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each do |file|
load file
end
end
end
end

View File

@@ -12,14 +12,10 @@ module Crono
jobs << job
end
def next
queue.first
end
private
def queue
jobs.sort_by(&:next)
def next_jobs
jobs.group_by(&:next).sort_by {|time,_| time }.first
end
end
mattr_accessor :scheduler
end

View File

@@ -1,3 +1,3 @@
module Crono
VERSION = '0.8.0'
VERSION = '0.8.8.pre'
end

View File

@@ -0,0 +1,16 @@
namespace :crono do
desc 'Clean unused job stats from DB'
task clean: :environment do
Crono.scheduler = Crono::Scheduler.new
Crono::Cronotab.process
current_job_ids = Crono.scheduler.jobs.map(&:job_id)
Crono::CronoJob.where.not(job_id: current_job_ids).destroy_all
end
desc 'Check cronotab.rb syntax'
task check: :environment do
Crono.scheduler = Crono::Scheduler.new
Crono::Cronotab.process
puts 'Syntax ok'
end
end

View File

@@ -0,0 +1,12 @@
# This is an example of a bad cronotab for tests
class TestJob
def perform
puts 'Test!'
end
end
# This is an error, because you can use `on` options with
# a period less than 7 days.
Crono.perform(TestJob).every 5.days, on: :sunday

View File

@@ -0,0 +1,9 @@
# This is an example of a good cronotab for tests
class TestJob
def perform
puts 'Test!'
end
end
Crono.perform(TestJob).every 5.seconds

View File

@@ -1,11 +1,6 @@
require 'spec_helper'
require 'crono/cli'
class TestJob
def perform
end
end
describe Crono::CLI do
let(:cli) { Crono::CLI.instance }
@@ -15,6 +10,7 @@ describe Crono::CLI do
expect(cli).to receive(:start_working_loop)
expect(cli).to receive(:parse_options)
expect(cli).to receive(:write_pid)
expect(Crono::Cronotab).to receive(:process)
cli.run
end
end

View File

@@ -1,15 +1,42 @@
require 'spec_helper'
describe Crono::Config do
let(:config) { Crono::Config.new }
describe '#initialize' do
it 'should initialize with default configuration options' do
ENV['RAILS_ENV'] = 'test'
@config = Crono::Config.new
expect(@config.cronotab).to be Crono::Config::CRONOTAB
expect(@config.logfile).to be Crono::Config::LOGFILE
expect(@config.pidfile).to be Crono::Config::PIDFILE
expect(@config.pidfile).to be nil
expect(@config.daemonize).to be false
expect(@config.environment).to be_eql ENV['RAILS_ENV']
end
describe "#pidfile" do
subject(:pidfile) { config.pidfile }
context "not explicity configured" do
context "daemonize is false" do
before { config.daemonize = false }
specify { expect(pidfile).to be_nil }
end
context "daemonize is true" do
before { config.daemonize = true }
specify { expect(pidfile).to eq Crono::Config::PIDFILE }
end
end
context "explicity configured" do
let(:path) { "foo/bar/pid.pid" }
before { config.pidfile = path }
specify { expect(pidfile).to eq path }
end
end
end
end

7
spec/crono_spec.rb Normal file
View File

@@ -0,0 +1,7 @@
require 'spec_helper'
describe Crono do
it 'has a version number' do
expect(Crono::VERSION).not_to be nil
end
end

20
spec/cronotab_spec.rb Normal file
View File

@@ -0,0 +1,20 @@
require 'spec_helper'
describe Crono::Cronotab do
describe '#process' do
it 'should load cronotab file' do
cronotab_path = File.expand_path('../assets/good_cronotab.rb', __FILE__)
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
expect {
Crono::Cronotab.process(cronotab_path)
}.to_not raise_error
end
it 'should raise error when cronotab is invalid' do
cronotab_path = File.expand_path('../assets/bad_cronotab.rb', __FILE__)
expect {
Crono::Cronotab.process(cronotab_path)
}.to raise_error
end
end
end

View File

@@ -1,18 +1,7 @@
require 'spec_helper'
class TestJob
def perform
end
end
class TestFailingJob
def perform
fail 'Some error'
end
end
describe Crono::Job do
let(:period) { Crono::Period.new(2.day) }
let(:period) { Crono::Period.new(2.day, at: '15:00') }
let(:job) { Crono::Job.new(TestJob, period) }
let(:failing_job) { Crono::Job.new(TestFailingJob, period) }
@@ -21,6 +10,12 @@ describe Crono::Job do
expect(job.period).to be period
end
describe '#next' do
it 'should return next performing time according to period' do
expect(job.next).to be_eql period.next
end
end
describe '#perform' do
after { job.send(:model).destroy }
@@ -37,11 +32,6 @@ describe Crono::Job do
expect(saved_log).to include 'Some error'
end
it 'should set Job#healthy to true if perform ok' do
job.perform.join
expect(job.healthy).to be true
end
it 'should set Job#healthy to false if perform with error' do
failing_job.perform.join
expect(failing_job.healthy).to be false
@@ -50,7 +40,7 @@ describe Crono::Job do
describe '#description' do
it 'should return job identificator' do
expect(job.description).to be_eql('Perform TestJob every 2 days')
expect(job.description).to be_eql('Perform TestJob every 2 days at 15:00')
end
end

View File

@@ -1,10 +1,5 @@
require 'spec_helper'
class TestJob
def perform
end
end
describe Crono::PerformerProxy do
it 'should add job to schedule' do
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))

View File

@@ -1,12 +1,6 @@
require 'spec_helper'
describe Crono::Period do
around(:each) do |example|
Timecop.freeze do
example.run
end
end
describe '#description' do
it 'should return period description' do
@period = Crono::Period.new(1.week, on: :monday, at: '15:20')
@@ -53,28 +47,33 @@ describe Crono::Period do
end
context 'in daily basis' do
it "should return Time.now if the next time in past" do
@period = Crono::Period.new(1.day, at: '06:00')
expect(@period.next(since: 2.days.ago).to_s).to be_eql(Time.now.to_s)
end
it 'should return the time 2 days from now' do
@period = Crono::Period.new(2.day)
expect(@period.next).to be_eql(2.day.from_now)
expect(@period.next.to_s).to be_eql(2.days.from_now.to_s)
end
it "should set time to 'at' time as a string" do
time = 10.minutes.ago
at = [time.hour, time.min].join(':')
@period = Crono::Period.new(2.day, at: at)
expect(@period.next).to be_eql(2.day.from_now.change(hour: time.hour, min: time.min))
@period = Crono::Period.new(2.days, at: at)
expect(@period.next).to be_eql(2.days.from_now.change(hour: time.hour, min: time.min))
end
it "should set time to 'at' time as a hash" do
time = 10.minutes.ago
at = { hour: time.hour, min: time.min }
@period = Crono::Period.new(2.day, at: at)
expect(@period.next).to be_eql(2.day.from_now.change(at))
@period = Crono::Period.new(2.days, at: at)
expect(@period.next).to be_eql(2.days.from_now.change(at))
end
it "should raise error when 'at' is wrong" do
expect {
Crono::Period.new(2.day, at: 1)
Crono::Period.new(2.days, at: 1)
}.to raise_error("Unknown 'at' format")
end
@@ -85,14 +84,14 @@ describe Crono::Period do
end
it 'should return time in relation to last time' do
@period = Crono::Period.new(2.day)
expect(@period.next(since: 1.day.ago)).to be_eql(1.day.from_now)
@period = Crono::Period.new(2.days)
expect(@period.next(since: 1.day.ago).to_s).to be_eql(1.day.from_now.to_s)
end
it 'should return today time if it is first run and not too late' do
time = 10.minutes.from_now
at = { hour: time.hour, min: time.min }
@period = Crono::Period.new(2.day, at: at)
@period = Crono::Period.new(2.days, at: at)
expect(@period.next.utc.to_s).to be_eql(Time.now.change(at).utc.to_s)
end
end

View File

@@ -1,32 +1,56 @@
require 'spec_helper'
class TestJob
def perform
end
end
describe Crono::Scheduler do
before(:each) do
@scheduler = Crono::Scheduler.new
@jobs = [
Crono::Period.new(3.day, at: 10.minutes.from_now.strftime('%H:%M')),
Crono::Period.new(1.day, at: 20.minutes.from_now.strftime('%H:%M')),
Crono::Period.new(7.day, at: 40.minutes.from_now.strftime('%H:%M'))
].map { |period| Crono::Job.new(TestJob, period) }
@scheduler.jobs = @jobs
end
let(:scheduler) { Crono::Scheduler.new }
describe '#add_job' do
it 'should call Job#load on Job' do
@job = Crono::Job.new(TestJob, Crono::Period.new(10.day, at: '04:05'))
expect(@job).to receive(:load)
@scheduler.add_job(@job)
scheduler.add_job(@job)
end
end
describe '#next' do
describe '#next_jobs' do
it 'should return next job in schedule' do
expect(@scheduler.next).to be @jobs[0]
scheduler.jobs = jobs = [
Crono::Period.new(3.days, at: 10.minutes.from_now.strftime('%H:%M')),
Crono::Period.new(1.day, at: 20.minutes.from_now.strftime('%H:%M')),
Crono::Period.new(7.days, at: 40.minutes.from_now.strftime('%H:%M'))
].map { |period| Crono::Job.new(TestJob, period) }
time, jobs = scheduler.next_jobs
expect(jobs).to be_eql [jobs[0]]
end
it 'should return an array of jobs scheduled at same time with `at`' do
time = 5.minutes.from_now
scheduler.jobs = jobs = [
Crono::Period.new(1.day, at: time.strftime('%H:%M')),
Crono::Period.new(1.day, at: time.strftime('%H:%M')),
Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
].map { |period| Crono::Job.new(TestJob, period) }
time, jobs = scheduler.next_jobs
expect(jobs).to be_eql [jobs[0], jobs[1]]
end
it 'should handle a few jobs scheduled at same time without `at`' do
scheduler.jobs = jobs = [
Crono::Period.new(10.seconds),
Crono::Period.new(10.seconds),
Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
].map { |period| Crono::Job.new(TestJob, period) }
_, next_jobs = scheduler.next_jobs
expect(next_jobs).to be_eql [jobs[0]]
Timecop.travel(4.seconds.from_now)
expect(Thread).to receive(:new)
jobs[0].perform
_, next_jobs = scheduler.next_jobs
expect(next_jobs).to be_eql [jobs[1]]
end
end
end

View File

@@ -1,6 +1,8 @@
require 'bundler/setup'
Bundler.setup
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'timecop'
require 'byebug'
require 'crono'
@@ -13,3 +15,14 @@ ActiveRecord::Base.establish_connection(
ActiveRecord::Base.logger = Logger.new(STDOUT)
CreateCronoJobs.up
class TestJob
def perform
end
end
class TestFailingJob
def perform
fail 'Some error'
end
end

View File

@@ -0,0 +1,23 @@
require 'spec_helper'
require 'rake'
load 'tasks/crono_tasks.rake'
Rake::Task.define_task(:environment)
describe 'rake' do
describe 'crono:clean' do
it 'should clean unused tasks from DB' do
Crono::CronoJob.create!(job_id: 'used_job')
ENV['CRONOTAB'] = File.expand_path('../../assets/good_cronotab.rb', __FILE__)
Rake::Task['crono:clean'].invoke
expect(Crono::CronoJob.where(job_id: 'used_job')).not_to exist
end
end
describe 'crono:check' do
it 'should check cronotab syntax' do
ENV['CRONOTAB'] = File.expand_path('../../assets/bad_cronotab.rb', __FILE__)
expect { Rake::Task['crono:check'].invoke }.to raise_error
end
end
end

View File

@@ -6,6 +6,7 @@ describe Crono::Web do
let(:app) { Crono::Web }
before do
Crono::CronoJob.destroy_all
@test_job_id = 'Perform TestJob every 5 seconds'
@test_job_log = 'All runs ok'
@test_job = Crono::CronoJob.create!(
@@ -28,6 +29,18 @@ describe Crono::Web do
get '/'
expect(last_response.body).to include 'Error'
end
it 'should show a success mark when a job is healthy' do
@test_job.update(healthy: true)
get '/'
expect(last_response.body).to include 'Success'
end
it 'should show a pending mark when a job is pending' do
@test_job.update(healthy: nil)
get '/'
expect(last_response.body).to include 'Pending'
end
end
describe '/job/:id' do

View File

@@ -6,7 +6,7 @@
%tr
%th Job
%th Last performed at
%th
%th Status
%th
- @jobs.each do |job|
%tr
@@ -16,6 +16,12 @@
- if job.healthy == false
%a{ href: url("/job/#{job.id}") }
%span.label.label-danger Error
- if job.healthy == true
%a{ href: url("/job/#{job.id}") }
%span.label.label-success Success
- else
%a{ href: url("/job/#{job.id}") }
%span.label.label-default Pending
%td
%a{ href: url("/job/#{job.id}") }
Log