crono 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,9 @@
1
1
  module Crono
2
2
  mattr_accessor :logger
3
3
 
4
+ # Crono::Logging is a standart Ruby logger wrapper
4
5
  module Logging
5
- def set_log_to(logfile)
6
+ def logfile=(logfile)
6
7
  Crono.logger = Logger.new(logfile)
7
8
  end
8
9
 
@@ -1,8 +1,9 @@
1
1
  require 'active_record'
2
2
 
3
3
  module Crono
4
+ # Crono::CronoJob is a ActiveRecord model to store job state
4
5
  class CronoJob < ActiveRecord::Base
5
- self.table_name = "crono_jobs"
6
+ self.table_name = 'crono_jobs'
6
7
  validates :job_id, presence: true, uniqueness: true
7
8
  end
8
9
  end
@@ -1,4 +1,5 @@
1
1
  module Crono
2
+ # Crono::PerformerProxy is a proxy used in cronotab.rb semantic
2
3
  class PerformerProxy
3
4
  def initialize(performer, scheduler)
4
5
  @performer = performer
@@ -1,27 +1,53 @@
1
1
  module Crono
2
+ # Period describe frequency of performing a task
2
3
  class Period
3
- def initialize(period, at: nil)
4
+ DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday,
5
+ :sunday]
6
+
7
+ def initialize(period, at: nil, on: nil)
4
8
  @period = period
5
9
  @at_hour, @at_min = parse_at(at) if at
10
+ @on = parse_on(on) if on
6
11
  end
7
12
 
8
13
  def next(since: nil)
9
- if since.nil?
10
- @next = Time.now.change(time_atts)
11
- return @next if @next.future?
12
- since = Time.now
13
- end
14
-
15
- @period.since(since).change(time_atts)
14
+ return initial_next unless since
15
+ @next = @period.since(since)
16
+ @next = @next.beginning_of_week.advance(days: @on) if @on
17
+ @next.change(time_atts)
16
18
  end
17
19
 
18
20
  def description
19
21
  desc = "every #{@period.inspect}"
20
- desc += " at %.2i:%.2i" % [@at_hour, @at_min] if @at_hour && @at_min
22
+ desc += format(' at %.2i:%.2i', @at_hour, @at_min) if @at_hour && @at_min
23
+ desc += " on #{DAYS[@on].capitalize}" if @on
21
24
  desc
22
25
  end
23
26
 
27
+ private
28
+
29
+ def initial_next
30
+ next_time = initial_day.change(time_atts)
31
+ return next_time if next_time.future?
32
+ @period.from_now.change(time_atts)
33
+ end
34
+
35
+ def initial_day
36
+ return Time.now unless @on
37
+ day = Time.now.beginning_of_week.advance(days: @on)
38
+ return day if day.future?
39
+ @period.from_now.beginning_of_week.advance(days: @on)
40
+ end
41
+
42
+ def parse_on(on)
43
+ day_number = DAYS.index(on)
44
+ fail "Wrong 'on' day" unless day_number
45
+ fail "period should be at least 1 week to use 'on'" if @period < 1.week
46
+ day_number
47
+ end
48
+
24
49
  def parse_at(at)
50
+ fail "period should be at least 1 day to use 'at'" if @period < 1.day
25
51
  case at
26
52
  when String
27
53
  time = Time.parse(at)
@@ -29,13 +55,12 @@ module Crono
29
55
  when Hash
30
56
  return at[:hour], at[:min]
31
57
  else
32
- raise "Unknown 'at' format"
58
+ fail "Unknown 'at' format"
33
59
  end
34
60
  end
35
61
 
36
- private
37
62
  def time_atts
38
- {hour: @at_hour, min: @at_min}.compact
63
+ { hour: @at_hour, min: @at_min }.compact
39
64
  end
40
65
  end
41
66
  end
@@ -1,4 +1,5 @@
1
1
  module Crono
2
+ # Scheduler is a container for job list and queue
2
3
  class Scheduler
3
4
  attr_accessor :jobs
4
5
 
@@ -15,7 +16,8 @@ module Crono
15
16
  queue.first
16
17
  end
17
18
 
18
- private
19
+ private
20
+
19
21
  def queue
20
22
  jobs.sort_by(&:next)
21
23
  end
@@ -1,3 +1,3 @@
1
1
  module Crono
2
- VERSION = '0.7.0'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -1,18 +1,20 @@
1
1
  require 'haml'
2
2
  require 'sinatra/base'
3
+ require 'crono'
3
4
 
4
5
  module Crono
6
+ # Web is a Web UI Sinatra app
5
7
  class Web < Sinatra::Base
6
- set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
7
- set :public_folder, Proc.new { "#{root}/assets" }
8
- set :views, Proc.new { "#{root}/views" }
8
+ set :root, File.expand_path(File.dirname(__FILE__) + '/../../web')
9
+ set :public_folder, proc { "#{root}/assets" }
10
+ set :views, proc { "#{root}/views" }
9
11
 
10
12
  get '/' do
11
13
  @jobs = Crono::CronoJob.all
12
14
  haml :dashboard, format: :html5
13
15
  end
14
16
 
15
- get '/jobs/:id' do
17
+ get '/job/:id' do
16
18
  @job = Crono::CronoJob.find(params[:id])
17
19
  haml :job
18
20
  end
@@ -4,6 +4,7 @@ require 'rails/generators/active_record'
4
4
 
5
5
  module Crono
6
6
  module Generators
7
+ # rails generate crono:install
7
8
  class InstallGenerator < ::Rails::Generators::Base
8
9
  include Rails::Generators::Migration
9
10
 
@@ -11,15 +12,16 @@ module Crono
11
12
  ActiveRecord::Generators::Base.next_migration_number(path)
12
13
  end
13
14
 
14
- desc "Installs crono and generates the necessary configuration files"
15
- source_root File.expand_path("../templates", __FILE__)
15
+ desc 'Installs crono and generates the necessary configuration files'
16
+ source_root File.expand_path('../templates', __FILE__)
16
17
 
17
18
  def copy_config
18
19
  template 'cronotab.rb.erb', 'config/cronotab.rb'
19
20
  end
20
21
 
21
22
  def create_migrations
22
- migration_template 'migrations/create_crono_jobs.rb', 'db/migrate/create_crono_jobs.rb'
23
+ migration_template 'migrations/create_crono_jobs.rb',
24
+ 'db/migrate/create_crono_jobs.rb'
23
25
  end
24
26
  end
25
27
  end
@@ -7,9 +7,9 @@
7
7
  #
8
8
  # class TestJob
9
9
  # def perform
10
- # puts "Test!"
10
+ # puts 'Test!'
11
11
  # end
12
12
  # end
13
13
  #
14
- # Crono.perform(TestJob).every 2.days, at: "15:30"
14
+ # Crono.perform(TestJob).every 2.days, at: '15:30'
15
15
  #
@@ -4,6 +4,7 @@ class CreateCronoJobs < ActiveRecord::Migration
4
4
  t.string :job_id, null: false
5
5
  t.text :log
6
6
  t.datetime :last_performed_at
7
+ t.boolean :healthy
7
8
  t.timestamps null: false
8
9
  end
9
10
  add_index :crono_jobs, [:job_id], unique: true
@@ -1,15 +1,16 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
  require 'crono/cli'
3
3
 
4
4
  class TestJob
5
- def perform;end
5
+ def perform
6
+ end
6
7
  end
7
8
 
8
9
  describe Crono::CLI do
9
10
  let(:cli) { Crono::CLI.instance }
10
11
 
11
- describe "#run" do
12
- it "should try to initialize rails with #load_rails and start working loop" do
12
+ describe '#run' do
13
+ it 'should initialize rails with #load_rails and start working loop' do
13
14
  expect(cli).to receive(:load_rails)
14
15
  expect(cli).to receive(:start_working_loop)
15
16
  expect(cli).to receive(:parse_options)
@@ -18,34 +19,30 @@ describe Crono::CLI do
18
19
  end
19
20
  end
20
21
 
21
- describe "#start_working_loop" do
22
- it "should start working loop"
23
- end
24
-
25
- describe "#parse_options" do
26
- it "should set cronotab" do
27
- cli.send(:parse_options, ["--cronotab", "/tmp/cronotab.rb"])
28
- expect(cli.config.cronotab).to be_eql "/tmp/cronotab.rb"
22
+ describe '#parse_options' do
23
+ it 'should set cronotab' do
24
+ cli.send(:parse_options, ['--cronotab', '/tmp/cronotab.rb'])
25
+ expect(cli.config.cronotab).to be_eql '/tmp/cronotab.rb'
29
26
  end
30
27
 
31
- it "should set logfile" do
32
- cli.send(:parse_options, ["--logfile", "log/crono.log"])
33
- expect(cli.config.logfile).to be_eql "log/crono.log"
28
+ it 'should set logfile' do
29
+ cli.send(:parse_options, ['--logfile', 'log/crono.log'])
30
+ expect(cli.config.logfile).to be_eql 'log/crono.log'
34
31
  end
35
32
 
36
- it "should set pidfile" do
37
- cli.send(:parse_options, ["--pidfile", "tmp/pids/crono.0.log"])
38
- expect(cli.config.pidfile).to be_eql "tmp/pids/crono.0.log"
33
+ it 'should set pidfile' do
34
+ cli.send(:parse_options, ['--pidfile', 'tmp/pids/crono.0.log'])
35
+ expect(cli.config.pidfile).to be_eql 'tmp/pids/crono.0.log'
39
36
  end
40
37
 
41
- it "should set daemonize" do
42
- cli.send(:parse_options, ["--daemonize"])
38
+ it 'should set daemonize' do
39
+ cli.send(:parse_options, ['--daemonize'])
43
40
  expect(cli.config.daemonize).to be true
44
41
  end
45
42
 
46
- it "should set environment" do
47
- cli.send(:parse_options, ["--environment", "production"])
48
- expect(cli.config.environment).to be_eql("production")
43
+ it 'should set environment' do
44
+ cli.send(:parse_options, ['--environment', 'production'])
45
+ expect(cli.config.environment).to be_eql('production')
49
46
  end
50
47
  end
51
48
  end
@@ -1,15 +1,15 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Crono::Config do
4
- describe "#initialize" do
5
- it "should initialize with default configuration options" do
6
- ENV["RAILS_ENV"] = "test"
4
+ describe '#initialize' do
5
+ it 'should initialize with default configuration options' do
6
+ ENV['RAILS_ENV'] = 'test'
7
7
  @config = Crono::Config.new
8
8
  expect(@config.cronotab).to be Crono::Config::CRONOTAB
9
9
  expect(@config.logfile).to be Crono::Config::LOGFILE
10
10
  expect(@config.pidfile).to be Crono::Config::PIDFILE
11
11
  expect(@config.daemonize).to be false
12
- expect(@config.environment).to be_eql ENV["RAILS_ENV"]
12
+ expect(@config.environment).to be_eql ENV['RAILS_ENV']
13
13
  end
14
14
  end
15
15
  end
@@ -1,63 +1,77 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  class TestJob
4
- def perform;end
4
+ def perform
5
+ end
5
6
  end
6
7
 
7
8
  class TestFailingJob
8
9
  def perform
9
- raise "Some error"
10
+ fail 'Some error'
10
11
  end
11
12
  end
12
13
 
13
14
  describe Crono::Job do
14
15
  let(:period) { Crono::Period.new(2.day) }
15
- let(:job) { Crono::Job.new(TestJob, period) }
16
+ let(:job) { Crono::Job.new(TestJob, period) }
16
17
  let(:failing_job) { Crono::Job.new(TestFailingJob, period) }
17
18
 
18
- it "should contain performer and period" do
19
+ it 'should contain performer and period' do
19
20
  expect(job.performer).to be TestJob
20
21
  expect(job.period).to be period
21
22
  end
22
23
 
23
- describe "#perform" do
24
- it "should run performer in separate thread" do
24
+ describe '#perform' do
25
+ after { job.send(:model).destroy }
26
+
27
+ it 'should run performer in separate thread' do
25
28
  expect(job).to receive(:save)
26
29
  thread = job.perform.join
27
30
  expect(thread).to be_stop
28
- job.send(:model).destroy
29
31
  end
30
32
 
31
- it "should save performin errors to log" do
33
+ it 'should save performin errors to log' do
32
34
  thread = failing_job.perform.join
33
35
  expect(thread).to be_stop
34
36
  saved_log = Crono::CronoJob.find_by(job_id: failing_job.job_id).log
35
- expect(saved_log).to include "Some error"
37
+ expect(saved_log).to include 'Some error'
38
+ end
39
+
40
+ it 'should set Job#healthy to true if perform ok' do
41
+ job.perform.join
42
+ expect(job.healthy).to be true
43
+ end
44
+
45
+ it 'should set Job#healthy to false if perform with error' do
46
+ failing_job.perform.join
47
+ expect(failing_job.healthy).to be false
36
48
  end
37
49
  end
38
50
 
39
- describe "#description" do
40
- it "should return job identificator" do
41
- expect(job.description).to be_eql("Perform TestJob every 2 days")
51
+ describe '#description' do
52
+ it 'should return job identificator' do
53
+ expect(job.description).to be_eql('Perform TestJob every 2 days')
42
54
  end
43
55
  end
44
56
 
45
- describe "#save" do
46
- it "should save new job to DB" do
57
+ describe '#save' do
58
+ it 'should save new job to DB' do
47
59
  expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
48
60
  job.save
49
61
  expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
50
62
  end
51
63
 
52
- it "should update saved job" do
64
+ it 'should update saved job' do
53
65
  job.last_performed_at = Time.now
66
+ job.healthy = true
54
67
  job.save
55
68
  @crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
56
69
  expect(@crono_job.last_performed_at.utc.to_s).to be_eql job.last_performed_at.utc.to_s
70
+ expect(@crono_job.healthy).to be true
57
71
  end
58
72
 
59
- it "should save and truncate job log" do
60
- message = "test message"
73
+ it 'should save and truncate job log' do
74
+ message = 'test message'
61
75
  job.send(:log, message)
62
76
  job.save
63
77
  expect(job.send(:model).reload.log).to include message
@@ -65,31 +79,39 @@ describe Crono::Job do
65
79
  end
66
80
  end
67
81
 
68
- describe "#load" do
82
+ describe '#load' do
69
83
  before do
70
84
  @saved_last_performed_at = job.last_performed_at = Time.now
71
85
  job.save
72
86
  end
73
87
 
74
- it "should load last_performed_at from DB" do
88
+ it 'should load last_performed_at from DB' do
75
89
  @job = Crono::Job.new(TestJob, period)
76
90
  @job.load
77
91
  expect(@job.last_performed_at.utc.to_s).to be_eql @saved_last_performed_at.utc.to_s
78
92
  end
79
93
  end
80
94
 
81
- describe "#log" do
82
- it "should write log messages to both common and job log" do
83
- message = "Test message"
84
- expect(job.logger).to receive(:info).with(message)
85
- expect(job.job_logger).to receive(:info).with(message)
95
+ describe '#log' do
96
+ it 'should write log messages to both common and job log' do
97
+ message = 'Test message'
98
+ expect(job.logger).to receive(:log).with(Logger::INFO, message)
99
+ expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
86
100
  job.send(:log, message)
87
101
  end
88
102
 
89
- it "should write job log to Job#job_log" do
90
- message = "Test message"
103
+ it 'should write job log to Job#job_log' do
104
+ message = 'Test message'
91
105
  job.send(:log, message)
92
106
  expect(job.job_log.string).to include(message)
93
107
  end
94
108
  end
109
+
110
+ describe '#log_error' do
111
+ it 'should call log with ERROR severity' do
112
+ message = 'Test message'
113
+ expect(job).to receive(:log).with(message, Logger::ERROR)
114
+ job.send(:log_error, message)
115
+ end
116
+ end
95
117
  end
@@ -1,26 +1,26 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Crono::CronoJob do
4
4
  let(:valid_attrs) do
5
5
  {
6
- job_id: "Perform TestJob every 3 days"
6
+ job_id: 'Perform TestJob every 3 days'
7
7
  }
8
8
  end
9
9
 
10
- it "should validate presence of job_id" do
11
- @crono_job = Crono::CronoJob.new()
10
+ it 'should validate presence of job_id' do
11
+ @crono_job = Crono::CronoJob.new
12
12
  expect(@crono_job).not_to be_valid
13
13
  expect(@crono_job.errors.added?(:job_id, :blank)).to be true
14
14
  end
15
15
 
16
- it "should validate uniqueness of job_id" do
17
- Crono::CronoJob.create!(job_id: "TestJob every 2 days")
18
- @crono_job = Crono::CronoJob.create(job_id: "TestJob every 2 days")
16
+ it 'should validate uniqueness of job_id' do
17
+ Crono::CronoJob.create!(job_id: 'TestJob every 2 days')
18
+ @crono_job = Crono::CronoJob.create(job_id: 'TestJob every 2 days')
19
19
  expect(@crono_job).not_to be_valid
20
20
  expect(@crono_job.errors.added?(:job_id, :taken)).to be true
21
21
  end
22
22
 
23
- it "should save job_id to DB" do
23
+ it 'should save job_id to DB' do
24
24
  Crono::CronoJob.create!(valid_attrs)
25
25
  @crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
26
26
  expect(@crono_job).to be_present