crono 0.7.0 → 0.8.0

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.
@@ -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