Compare commits

..

250 Commits
v0.6.1 ... main

Author SHA1 Message Date
Dzmitry Plashchynski
6c7ff2f7b1 Bump 2.1.0 2024-12-18 14:58:42 +03:00
Dzmitry Plashchynski
2e663c2104 Merge pull request #127 from janko/rails-7.2
Add Rails 7.2 support
2024-08-19 21:57:22 +09:00
Janko Marohnić
ab4e1e808d Call clear_active_connections! on the connection handler
Active Record 7.2 removed the deprecated
`ActiveRecord::Base.clear_active_connections!` delegate method.
2024-08-19 14:49:59 +02:00
Janko Marohnić
9a8e8049f9 Add Propshaft for tests
Otherwise Crono doesn't have an asset pipeline, so the engine initializer
raises an error.
2024-08-19 14:49:25 +02:00
Dzmitry Plashchynski
6bf1ec9cfd Merge pull request #119 from petergoldstein/feature/add_ruby_3_2_to_ci
Adds Ruby 3.2 to the CI matrix
2024-05-23 23:10:03 +03:00
Dzmitry Plashchynski
d3dc79e4f1 Merge pull request #124 from cisolarix/patch-1
fix: typo
2024-05-23 01:58:42 +03:00
Dzmitry Plashchynski
e0f41858e0 Merge pull request #125 from simi/memory
Remove :memory file and fix in-memory SQLite setup.
2024-05-23 01:57:42 +03:00
Josef Šimánek
e277fd9fb7 Remove :memory file and fix in-memory SQLite setup.
- :memory file makes it problematic on Windows
2024-05-23 00:34:15 +02:00
Yanming Deng
d4cc325685 fix: typo 2024-04-15 21:28:23 +08:00
Chrıs Seelus
0bfdc6996b Merge pull request #123 from janko/allow-propshaft
Remove Sprockets hard dependency, allow Propshaft
2023-12-19 19:43:54 +01:00
Janko Marohnić
7dffc5c77b Remove Sprockets hard dependency, allow Propshaft 2023-12-15 17:44:15 +01:00
Peter Goldstein
bfbb8733cd Adds Ruby 3.2 to the CI matrix 2023-01-21 14:47:31 -05:00
Dzmitry Plashchynski
4294bad43a Update Gemfile.lock 2022-09-03 19:22:59 +03:00
Dzmitry Plashchynski
0702e20add Bump 2.0.1 2022-09-03 15:27:02 +03:00
Dzmitry Plashchynski
db8c29afbe Merge pull request #109 from itbeaver/fix_job_args
Fixes arguments for jobs
2022-09-03 15:22:26 +03:00
Aleksandr Bobrov
43e411cf0f Fix args 2022-09-03 00:26:10 +04:00
Dzmitry Plashchynski
e43ad4d409 Merge pull request #108 from plashchynski/dependabot/bundler/activerecord-7.0.3.1
Bump activerecord from 7.0.3 to 7.0.3.1
2022-07-24 22:52:25 +03:00
Dzmitry Plashchynski
c5c58b559f Write PID in the daemon mode 2022-07-24 22:50:05 +03:00
Dzmitry Plashchynski
81d1e3496f remove duplicated file 2022-07-24 22:44:45 +03:00
Dzmitry Plashchynski
ddbfa4c42e Fix specs 2022-07-24 22:38:08 +03:00
Dzmitry Plashchynski
e03d9b4ba6 Remove crono_test.sqlite from git cache 2022-07-24 22:37:50 +03:00
dependabot[bot]
811ab31978 Bump activerecord from 7.0.3 to 7.0.3.1
Bumps [activerecord](https://github.com/rails/rails) from 7.0.3 to 7.0.3.1.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.3.1/activerecord/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v7.0.3...v7.0.3.1)

---
updated-dependencies:
- dependency-name: activerecord
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-15 11:38:13 +00:00
Dzmitry Plashchynski
cb5064852a Merge pull request #107 from mediafinger/add-github-actions-for-specs
Add GitHub actions for specs
2022-07-15 14:37:21 +03:00
Andreas Finger
a421a6207b ci: Add GitHub Action to run specs 2022-07-11 10:43:02 +02:00
Andreas Finger
4abc999583 doc: List compatible Ruby versions in the README 2022-07-11 10:41:20 +02:00
Dzmitry Plashchynski
0e1dec2956 Merge pull request #106 from plashchynski/dependabot/bundler/rails-html-sanitizer-1.4.3
Bump rails-html-sanitizer from 1.4.2 to 1.4.3
2022-07-08 13:05:37 +03:00
dependabot[bot]
3a17938571 Bump rails-html-sanitizer from 1.4.2 to 1.4.3
Bumps [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/rails/rails-html-sanitizer/releases)
- [Changelog](https://github.com/rails/rails-html-sanitizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rails/rails-html-sanitizer/compare/v1.4.2...v1.4.3)

---
updated-dependencies:
- dependency-name: rails-html-sanitizer
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-06 12:23:15 +00:00
Dzmitry Plashchynski
5391930954 Merge branch 'main' of https://github.com/plashchynski/crono 2022-06-16 11:08:28 +02:00
Dzmitry Plashchynski
5c16c8c6dd Do not pass empty arguments to the worker. Closes #95 2022-06-16 11:08:22 +02:00
Dzmitry Plashchynski
b1cc71e9a4 Update README.md 2022-05-30 00:25:46 +03:00
Dzmitry Plashchynski
4d6e7b58b4 Merge branch 'main' of https://github.com/plashchynski/crono 2022-05-29 22:33:33 +03:00
Dzmitry Plashchynski
a3587816e1 Create SECURITY.md 2022-05-29 22:31:51 +03:00
Dzmitry Plashchynski
805fbc48e6 Create codeql-analysis.yml 2022-05-29 22:28:58 +03:00
Dzmitry Plashchynski
80d8713cb2 Bump 2.0.0 2022-05-29 22:25:03 +03:00
Dzmitry Plashchynski
f5b5169b1f Remove requirements as it pretty common 2022-05-29 22:22:02 +03:00
Dzmitry Plashchynski
0a1ec366e0 do not pass empty arguments to Job#perform if arguments are not specified 2022-05-29 22:21:23 +03:00
Dzmitry Plashchynski
fc18c8c787 Update README.md 2022-05-29 21:30:53 +03:00
Dzmitry Plashchynski
49b1d2b85d Remove unused badgets 2022-05-29 21:28:29 +03:00
Dzmitry Plashchynski
efc4254146 Merge branch 'master' of https://github.com/plashchynski/crono 2022-05-29 21:18:16 +03:00
Dzmitry Plashchynski
abbae2bb33 remove deprecated 2022-05-29 21:18:07 +03:00
Dzmitry Plashchynski
e4066cfe10 Merge pull request #92 from magynhard/patch-1
Rake example does only fire once
2022-05-29 20:35:32 +03:00
Dzmitry Plashchynski
9f1bd7bf72 Merge branch 'master' into patch-1 2022-05-29 20:35:27 +03:00
Dzmitry Plashchynski
5831e040fc Update Gemfile.lock 2022-05-29 20:33:12 +03:00
Dzmitry Plashchynski
258b258003 Merge pull request #91 from cseelus/convert-to-engine-rspec
Convert to Rails engine
2022-05-29 20:32:11 +03:00
Dzmitry Plashchynski
333f18d89b Merge branch 'master' into convert-to-engine-rspec 2022-05-29 20:32:06 +03:00
Dzmitry Plashchynski
be1ecf2995 Merge pull request #90 from cseelus/ruby-3-compatibility
Use Ruby 3 conversion kwargs
2022-05-29 20:28:39 +03:00
Dzmitry Plashchynski
3d7267d97e Merge pull request #74 from muZk/patch-1
fix: rails 4.0.x
2022-05-29 20:28:10 +03:00
Dzmitry Plashchynski
9e8a6f0e05 Update dependencies 2022-05-29 20:24:45 +03:00
Dzmitry Plashchynski
2232671d86 Merge branch 'master' of https://github.com/plashchynski/crono 2022-05-29 20:22:55 +03:00
Dzmitry Plashchynski
8ea181397e Bundle with bundler 2 2022-05-29 20:22:19 +03:00
Dzmitry Plashchynski
e500738fd4 Merge pull request #97 from plashchynski/dependabot/bundler/activesupport-7.0.3
Bump activesupport from 5.0.0.1 to 7.0.3
2022-05-29 19:46:14 +03:00
Dzmitry Plashchynski
4279cbe27a Merge pull request #98 from plashchynski/dependabot/bundler/activerecord-7.0.3
Bump activerecord from 5.0.0.1 to 7.0.3
2022-05-29 19:46:02 +03:00
Dzmitry Plashchynski
630137d575 Merge pull request #99 from plashchynski/dependabot/bundler/rack-2.2.3.1
Bump rack from 2.2.3 to 2.2.3.1
2022-05-29 19:45:45 +03:00
Dzmitry Plashchynski
ae78de420f Update crono.gemspec 2022-05-29 19:44:44 +03:00
Dzmitry Plashchynski
e6f5e4299b Update README.md 2022-05-29 19:07:44 +03:00
dependabot[bot]
a6f8354e89 Bump rack from 2.2.3 to 2.2.3.1
Bumps [rack](https://github.com/rack/rack) from 2.2.3 to 2.2.3.1.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/2.2.3...2.2.3.1)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-27 17:59:38 +00:00
dependabot[bot]
610046d0fe Bump activerecord from 5.0.0.1 to 7.0.3
Bumps [activerecord](https://github.com/rails/rails) from 5.0.0.1 to 7.0.3.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.3/activerecord/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v5.0.0.1...v7.0.3)

---
updated-dependencies:
- dependency-name: activerecord
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-20 09:42:14 +00:00
dependabot[bot]
0391dac2e9 Bump activesupport from 5.0.0.1 to 7.0.3
Bumps [activesupport](https://github.com/rails/rails) from 5.0.0.1 to 7.0.3.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.3/activesupport/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v5.0.0.1...v7.0.3)

---
updated-dependencies:
- dependency-name: activesupport
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-20 09:42:13 +00:00
Dzmitry Plashchynski
a74da12862 Merge pull request #96 from plashchynski/dependabot/bundler/sinatra-2.2.0
Bump sinatra from 1.4.7 to 2.2.0
2022-05-20 12:41:16 +03:00
Dzmitry Plashchynski
7573109519 Update README.md 2022-05-20 12:40:54 +03:00
dependabot[bot]
a8e80b6160 Bump sinatra from 1.4.7 to 2.2.0
Bumps [sinatra](https://github.com/sinatra/sinatra) from 1.4.7 to 2.2.0.
- [Release notes](https://github.com/sinatra/sinatra/releases)
- [Changelog](https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sinatra/sinatra/compare/v1.4.7...v2.2.0)

---
updated-dependencies:
- dependency-name: sinatra
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-03 21:13:19 +00:00
Matthäus J. N. Beyrle
0af1645456 update rake example 2021-02-19 14:33:46 +01:00
Chris Seelus
ee79596509 Cleanup and get rspec to run again 2021-02-11 17:37:10 +01:00
Chris Seelus
6fee31fc81 Fix tests 2021-02-11 01:59:28 +01:00
Chris Seelus
551572fe56 Move CronoJob to models dir 2021-02-11 01:59:10 +01:00
Chris Seelus
d53520bbf0 Use Rails plugin generator and adapt 2021-02-10 22:10:43 +01:00
Chris Seelus
87b5726919 Use Ruby 3 conversion kwargs 2021-02-10 16:57:48 +01:00
Dzmitry Plashchynski
9b9193e1c8 Merge pull request #80 from andrisbriedis/master
Fixed race condition when starting jobs
2019-07-10 14:15:04 +03:00
Andris Briedis
6ca28ae01d Fixed race condition when starting jobs
Calling Time.zone.now in the if statement and while calculating sleep delay sometimes resulted in an exception with message "time interval must be positive".
This happened if job was about to start and we needed to sleep for a really short period of time + server was under high load.
First the application was checking if difference between current time and scheduled run time was positive.
If the expression returned true, sleep was called, but required delay was calculated once again and could result in a negative value being passed to the sleep function.
2019-07-10 11:43:04 +03:00
Nicolás Gómez
11350e83d9 fix: rails 4.0.x
Patch time_atts when `compact` is not defined in Hash class.
2018-04-11 10:50:45 -03:00
Dzmitry Plashchynski
3344c431a9 Merge pull request #66 from redrick/master
Add migration version for Rails 5 and above
2017-10-19 21:19:48 +03:00
Andrej Antas
7c8246087c Add migration version for Rails 5 and above
Fixes #65
2017-10-16 00:09:59 +02:00
Dzmitry Plashchynski
4136a5af1a Update LICENSE 2017-03-28 18:31:21 +03:00
Dzmitry Plashchynski
5b72e08222 Merge pull request #59 from haoxilu/master
rails timezone support
2017-03-10 13:58:01 +02:00
Dzmitry Plashchynski
5c0c2ee195 Update .travis.yml 2017-03-10 13:51:35 +02:00
haoxilu
d72a2d2b07 write some annotation 2017-03-07 23:02:42 +08:00
haoxilu
258052b852 rails timezone support 2017-02-24 09:05:43 +08:00
Dzmitry Plashchynski
a7488df85b Bump v1.1.2 2016-12-02 03:42:32 +02:00
Dzmitry Plashchynski
c777933044 Fix PG text limit. Closes #54 2016-12-02 03:41:38 +02:00
Dzmitry Plashchynski
0c2f52d9bc Bump 1.1.1 2016-11-30 01:06:29 +02:00
Dzmitry Plashchynski
33e9794621 Last log instead of Log since it can be truncated 2016-11-30 01:05:47 +02:00
Dzmitry Plashchynski
c336b6d00b Fix empty job_options error 2016-11-30 00:52:54 +02:00
Dzmitry Plashchynski
1cf37ee30f Bump 1.1.0 2016-11-30 00:43:52 +02:00
Dzmitry Plashchynski
137dfe6d19 Fix MySQL default text size limit 2016-11-30 00:16:58 +02:00
Dzmitry Plashchynski
14c3e3162e Log truncating Close #41 2016-11-30 00:11:56 +02:00
Dzmitry Plashchynski
d1e15b8537 Ignore .byebug_history 2016-11-30 00:01:54 +02:00
Dzmitry Plashchynski
f5d65b6cc7 Job options 2016-11-29 15:41:39 +02:00
Dzmitry Plashchynski
64dde82342 Update gems 2016-11-29 15:39:49 +02:00
Dzmitry Plashchynski
e331490c48 Update ruby versions for travis 2016-11-29 15:35:09 +02:00
Dzmitry Plashchynski
8a61e65963 Merge pull request #53 from acolyer/same_day
schedule on: today if at: time not passed
2016-11-16 23:25:56 +02:00
Adrian Colyer
3f9ea8350d schedule today if at: time not passed 2016-11-06 15:47:50 +00:00
Dzmitry Plashchynski
1d9d30ef89 Fix issue link 2016-09-22 18:39:56 +03:00
Dzmitry Plashchynski
72810a95ae Add information about the showexceptions issue. Closes #1055 2016-09-22 18:38:44 +03:00
Dzmitry Plashchynski
4401c6bba4 Merge branch 'master' of github.com:plashchynski/crono 2016-09-22 18:24:56 +03:00
Dzmitry Plashchynski
e7df4bb067 Fix crash when no jobs defined 2016-09-22 18:24:46 +03:00
Dzmitry Plashchynski
935bdebd84 Merge pull request #51 from pachacamac/patch-1
deprecate need to config your app name.
2016-09-22 18:08:51 +03:00
Marc
b22845cba6 deprecate need to config your app name.
Instead of `<AppName>::Application.load_tasks` the same can be achieved by `Rails.app_class.load_tasks`, therefore not requiring the user to configure the AppName. Less margin for errors :)
2016-09-17 12:38:48 +02:00
Dzmitry Plashchynski
77481f1143 Fix asterisk 2016-07-02 21:27:55 +03:00
Dzmitry Plashchynski
42dc1bbc4f Remove gitter 2016-07-02 21:23:34 +03:00
Dzmitry Plashchynski
63c52896f7 Since Rails 5 requires Ruby 2.2.2 or newer. 2016-07-01 04:46:02 +03:00
Dzmitry Plashchynski
977f49a3a4 Rails 3 is not supported anymore 2016-07-01 04:40:00 +03:00
Dzmitry Plashchynski
7155ce797c Bump 1.0.3 2016-07-01 04:36:45 +03:00
Dzmitry Plashchynski
d18866564a Update change log 2016-07-01 04:35:56 +03:00
Dzmitry Plashchynski
b39be015b1 Rails 5 supports "week" in time notations 2016-07-01 04:35:09 +03:00
Dzmitry Plashchynski
51c914ea8a Tested on Rails 5, closes #49 2016-07-01 04:04:48 +03:00
Dzmitry Plashchynski
ac71db0d68 Added Known Issues section to Readme. Closes #43 2016-06-26 15:50:09 +03:00
Dzmitry Plashchynski
4e3ca885a8 Fix table_name_suffix/prefix issue. Closes #33 2016-06-26 02:12:04 +03:00
Dzmitry Plashchynski
8c998794b4 Bump 1.0.1 2016-06-24 13:36:20 +03:00
Dzmitry Plashchynski
de070d5bfa Fix job saving 2016-06-24 13:33:57 +03:00
Dzmitry Plashchynski
e1c143db60 Fix typo 2016-06-24 02:43:15 +03:00
Dzmitry Plashchynski
ad6d6cf9d6 Exclude ruby-2.3 from osx since it isn't installed on Travis 2016-06-24 02:41:10 +03:00
Dzmitry Plashchynski
01cc0864f2 Fix README grammar 2016-06-24 02:23:12 +03:00
Dzmitry Plashchynski
87deadf00e Update gems 2016-06-24 02:17:29 +03:00
Dzmitry Plashchynski
3e5ff2871d Add ruby 2.3 to test on Travis 2016-06-24 02:16:24 +03:00
Dzmitry Plashchynski
b2f107f83f Fix Travis build 2016-06-24 02:10:13 +03:00
Dzmitry Plashchynski
33867f1a25 Merge pull request #47 from adamico/master
Fix for Rails 5
2016-04-18 03:00:35 +03:00
Andrea D'Amico
f240036fcd Fix for Rails 5 2016-04-14 16:16:05 +02:00
Dzmitry Plashchynski
fa69ed0fd9 Bump version to 1.0.0 2016-03-30 16:41:31 +03:00
Dzmitry Plashchynski
95d8e13563 Update gems 2016-03-30 16:40:29 +03:00
Dzmitry Plashchynski
63283509cb Update gems 2016-01-29 18:08:14 +02:00
Dzmitry Plashchynski
16ca450033 Fix option parser 2016-01-20 20:41:26 +02:00
Dzmitry Plashchynski
acbfea2308 Bump 1.0.0.pre2 2016-01-20 16:20:59 +02:00
Dzmitry Plashchynski
b1695964a1 Fix rails loading issue 2016-01-20 15:29:16 +02:00
Dzmitry Plashchynski
fba29d80e2 Bump 1.0.0.pre 2016-01-20 01:23:51 +02:00
Dzmitry Plashchynski
8d6e9e3854 Add log to .gitignore 2016-01-20 01:21:07 +02:00
Dzmitry Plashchynski
c0feafa099 Update gems 2016-01-20 01:20:33 +02:00
Dzmitry Plashchynski
3d71df3d2f Merge pull request #36 from preisanalytics/jhuebl_add_data_to_scheduled_job_20160113
Add the possibility to schedule jobs with arguments
2016-01-16 01:18:22 +02:00
Dzmitry Plashchynski
4c223e1bf6 Merge pull request #34 from lhz/intervals
Added :within option to Period to run only within given time interval.
2016-01-16 01:17:54 +02:00
Dzmitry Plashchynski
65cc443f1d Merge pull request #37 from preisanalytics/jhuebl_add_start_stop_and_restart_to_cli_20160114
add start stop and restart to cli
2016-01-16 01:17:16 +02:00
Jannis Hübl
5d9b420582 fix build and config_spec 2016-01-15 11:34:32 +01:00
Jannis Hübl
4a0c2d78e2 add documentation and fix --help 2016-01-15 11:18:53 +01:00
Jannis Hübl
0affff21d1 add start|stop|restart|run to crono executable
this is done using daemonize gem, but it is not breaking the old
interface. But sets a deprected comment to the old one daemonize
process.
2016-01-15 11:03:38 +01:00
Jannis Hübl
042228900f document how to schedule jobs with arguments 2016-01-14 10:23:46 +01:00
Jannis Hübl
89f3b9a8a1 change to args which will be passed to Job#perform 2016-01-14 09:33:14 +01:00
Jannis Hübl
dd4f92b569 add data to Cron.perform which will be passed to ExampleJob.new(data).perform 2016-01-13 17:44:27 +01:00
Lars Haugseth
4b7b03f8a1 Convert TimeOfDay values to UTC. 2015-10-03 16:34:27 +02:00
Lars Haugseth
a93b937d14 Added :within option to Period to run only within given time interval. 2015-10-03 15:08:43 +02:00
Dzmitry Plashchynski
6881109934 Fix .travis.yml 2015-09-25 12:09:34 +03:00
Dzmitry Plashchynski
ffe49c0557 Update rack 2015-09-21 01:52:33 +03:00
Dzmitry Plashchynski
ecc83c5142 Update Gemfile.lock 2015-09-21 01:51:25 +03:00
Dzmitry Plashchynski
1d25475686 Merge pull request #32 from Natural-Intelligence/support_multiple_nodes
Support multiple nodes
2015-09-21 01:49:19 +03:00
avi_alima
e416113ac2 Add ability to define minimal time between job executions to support multiple corno nodes, so two different nodes will not execute the same job
Add Locking for the case that two nodes start perform job together.

If execution_interval == 0.minutes, skip locking and immediately perform
2015-08-20 13:35:41 +03:00
avi_alima
3a480a7d9a Add ability to define minimal time between job executions to support multiple corno nodes, so two different nodes will not execute the same job
Add Locking for the case that two nodes start perform job together.
2015-08-20 12:54:16 +03:00
avi_alima
32bdba3244 Add ability to define minimal time between job executions to support multiple corno nodes, so two different nodes will not execute the same job 2015-08-19 18:08:48 +03:00
Dzmitry Plashchynski
f76dff32e4 Merge pull request #28 from ChandravatiSG/db_connection_pool_fix
#27 Fixed DB connection pool issue.
2015-06-17 15:53:41 +03:00
ChandravatiSG
eaa3a872bf #27 Fixed DB connection pool issue. 2015-06-17 18:11:41 +05:30
Dzmitry Plashchynski
6b627275d8 v0.9.0 2015-05-29 23:29:46 +03:00
Dzmitry Plashchynski
00d5c777dd Merge branch 'master' of github.com:plashchynski/crono 2015-05-28 17:43:12 +03:00
Dzmitry Plashchynski
c28a0bbc8a Able to specify minutes for hour-based schedule. Closing #26 2015-05-28 17:43:02 +03:00
Dzmitry Plashchynski
45c22ee6ba Merge pull request #24 from rogercampos/feature/no-activejob-dependency
activejob is not a real dependency
2015-05-12 13:33:13 +03:00
Roger Campos
2ac14113b6 activejob is not a real dependency 2015-05-11 18:03:56 +02:00
Dzmitry Plashchynski
f909873165 v0.8.9 2015-04-14 13:48:51 +03:00
Dzmitry Plashchynski
94fed61c8a Update web ui preview 2015-04-13 18:05:29 +03:00
Dzmitry Plashchynski
ddf7127b27 Update web ui preview 2015-04-13 18:01:37 +03:00
Dzmitry Plashchynski
cd7e842fd2 v0.8.9.pre 2015-04-13 17:52:31 +03:00
Dzmitry Plashchynski
ad8794c497 Prefixes for Safari 2015-04-13 17:52:02 +03:00
Dzmitry Plashchynski
f711b6b450 Fix error message 2015-04-13 17:31:53 +03:00
Dzmitry Plashchynski
c2445d831b Fix specs 2015-04-13 16:45:38 +03:00
Dzmitry Plashchynski
1a5fd351b4 Merge branch 'materialize_css' 2015-04-13 16:43:24 +03:00
Dzmitry Plashchynski
aee028919c v0.8.8.pre 2015-04-13 16:18:18 +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
5e944ec375 Local css and js 2015-04-13 12:23:15 +03:00
Dzmitry Plashchynski
f2f98bbb76 Merge branch 'master' into materialize_css 2015-04-12 17:10:45 +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
9c984cda49 Reduce header size 2015-04-12 03:56:44 +03:00
Dzmitry Plashchynski
dbb4f374d7 Facelift 2015-04-12 03:55:39 +03:00
Dzmitry Plashchynski
f245f33533 Do not show link to log if a job has never performed yet 2015-04-12 03:40:54 +03:00
Dzmitry Plashchynski
742cdc00c7 Status labels 2015-04-12 01:26:40 +03:00
Dzmitry Plashchynski
81ee422e30 Materialize css 2015-04-12 01:09:56 +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
Dzmitry Plashchynski
f57d440424 Bump 0.8.0 2015-03-15 09:57:18 +02:00
Dzmitry Plashchynski
95a237aeb5 Check period duration to be at least 1 day when using 'at' 2015-03-15 09:56:39 +02:00
Dzmitry Plashchynski
6508197f26 Remove unused spec 2015-03-15 09:35:16 +02:00
Dzmitry Plashchynski
a0c612fb27 Use on in a period description 2015-03-15 09:34:27 +02:00
Dzmitry Plashchynski
a3c4ec87f5 Added on to README.md 2015-03-15 09:31:46 +02:00
Dzmitry Plashchynski
9b85c8b8c3 Added 'on' option for Period 2015-03-15 09:29:02 +02:00
Dzmitry Plashchynski
1af691ef24 Lint web 2015-03-14 03:26:32 +02:00
Dzmitry Plashchynski
8a89a9a8eb CLI refactoring 2015-03-14 03:18:16 +02:00
Dzmitry Plashchynski
7e2e65e21b Job refactoring 2015-03-14 03:07:49 +02:00
Dzmitry Plashchynski
4205b12fe1 Fix installation instructions 2015-03-13 23:34:58 +02:00
Dzmitry Plashchynski
bb9f62a69b Fix loading issue 2015-03-13 23:28:10 +02:00
Dzmitry Plashchynski
a9798acb35 Lint the whole project 2015-03-13 20:46:54 +02:00
Dzmitry Plashchynski
1aa27baca8 Lint crono.gemspec 2015-03-13 19:51:32 +02:00
Dzmitry Plashchynski
af1e1959ac Update README.md 2015-03-12 18:21:13 +02:00
Dzmitry Plashchynski
7af68aa591 Update README.md 2015-03-11 00:14:28 +02:00
Dzmitry Plashchynski
6d2e3fbb75 Add job#log_error, log error with ERROR severity 2015-03-10 23:59:39 +02:00
Dzmitry Plashchynski
46fe2548d9 Refactoring 2015-03-10 23:49:38 +02:00
Dzmitry Plashchynski
6859d1f09a Add specs to test error marks 2015-03-10 23:37:15 +02:00
Dzmitry Plashchynski
6674bca0be Refactoring 2015-03-10 23:33:14 +02:00
Dzmitry Plashchynski
5a5c29c52a Mark job as error when it's not healthy 2015-03-10 23:26:58 +02:00
Dzmitry Plashchynski
a24389f6cc Add Job#healthy 2015-03-10 00:19:33 +02:00
Dzmitry Plashchynski
54a1b53af9 Add tests for Crono::Web 2015-03-09 23:43:17 +02:00
Dzmitry Plashchynski
eedff96d39 Autoload Crono::Web 2015-03-09 00:24:58 +02:00
Dzmitry Plashchynski
bb9ab77c1d Bump 0.7.0 2015-03-08 22:53:08 +02:00
Dzmitry Plashchynski
1b5479044f Add web ui screenshot 2015-03-08 22:43:36 +02:00
Dzmitry Plashchynski
9133a664bd Update screenshot 2015-03-08 22:42:19 +02:00
Dzmitry Plashchynski
a667c6ca24 Add crono UI example 2015-03-08 22:30:48 +02:00
Dzmitry Plashchynski
b95c480a8f Add background for web 2015-03-08 22:25:55 +02:00
Dzmitry Plashchynski
ba57834f68 Add layout for the web dashboard 2015-03-08 21:21:06 +02:00
Dzmitry Plashchynski
4fb45724d6 Able to see job log 2015-03-08 00:24:33 +02:00
Dzmitry Plashchynski
bb03a562cc Add simple web interface 2015-03-08 00:03:59 +02:00
Dzmitry Plashchynski
c0b25b2a7c Replace the file DB with shared cached memory DB 2015-03-07 18:50:34 +02:00
Dzmitry Plashchynski
f43ae4b1b1 Save performing errors to DB 2015-03-07 18:05:47 +02:00
Dzmitry Plashchynski
00e51604ae Use sqlite3 file database 2015-03-07 18:03:50 +02:00
Dzmitry Plashchynski
b4d15f7909 Job semaphore 2015-03-07 17:05:51 +02:00
Dzmitry Plashchynski
b3920fa2ee Use Mutex to synchronize access to Job's log 2015-03-07 16:38:11 +02:00
Dzmitry Plashchynski
5652d19e62 Write Job#job_log 2015-03-07 16:24:20 +02:00
Dzmitry Plashchynski
dc1f55e13b Add byebug 2015-03-07 16:24:01 +02:00
Dzmitry Plashchynski
98f058767f Fix spec 2015-03-07 16:06:40 +02:00
Dzmitry Plashchynski
5db94543d1 Fix specs 2015-03-07 04:23:58 +02:00
Dzmitry Plashchynski
3e5363f560 Fix specs 2015-03-07 04:18:54 +02:00
Dzmitry Plashchynski
6d90cb3233 Period#next should return today time if it is first run and not too late 2015-03-07 04:13:59 +02:00
Dzmitry Plashchynski
368cdde296 Fix Ruby 2.0.0 time comparsion issue 2015-03-07 03:54:35 +02:00
Dzmitry Plashchynski
84717493a8 Simplify text in examples 2015-03-07 03:49:29 +02:00
78 changed files with 2223 additions and 427 deletions

72
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '36 15 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'ruby' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

36
.github/workflows/rspec.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: "RSpec"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '00 12 * * *' # daily at 12:00
jobs:
specs:
name: specs
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
ruby: ['2.7', '3.0', '3.1', '3.2'] # Due to https://github.com/actions/runner/issues/849, we have to use quotes
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Ruby and install gems
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
cache-version: 1 # change this value when you have to empty the cache manually
- name: Run specs
run: |
bundle exec rspec spec/

17
.gitignore vendored
View File

@@ -1,3 +1,14 @@
pkg/*
*.gem
.bundle
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
log/*.log
.byebug_history
spec/internal
# macOS stuff
.DS_Store

3
.rspec
View File

@@ -1,2 +1,3 @@
--color
--require spec_helper
--format documentation
--color

View File

@@ -1,15 +0,0 @@
language: ruby
os:
- linux
- osx
rvm:
- 2.0.0
- 2.1
- 2.2
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/907e95dada362be2a13c
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View File

@@ -1,19 +1,104 @@
0.5.0
2.1.0
-----------
- Add Rails 7.2 support (thanks to @janko)
2.0.1
-----------
- Fix a job argument error
2.0.0
-----------
- Converted this gem to a proper Rails engine
- Gets rid of `sinatra` and `haml` dependencies for the Web UI
- Web UI is now responsive and thus usable on smartphones, etc.
- Fixed crash with Ruby 3.0
1.1.0
-----------
- Rails 3 and old Rubies are not supported anymore, sorry rails 3 guys...
- Requires Ruby 2.2.2 or newer
- Fixed crash when no jobs defined in your cronotab
- Some doc updates (thanks to @pachacamac)
- Job will schedule on: today if at: time not passed (thanks to @acolyer)
- Job log truncating (thanks to @reiz)
1.0.3
-----------
- "every 1 week" jobs now displaying on Rails 5 as "1 week" not as "7 days"
- Liberal gem dependencies to support both Rails 4 and Rails 5
1.0.2
-----------
- Fix table_name_suffix/prefix issue: https://github.com/plashchynski/crono/issues/33
1.0.1
-----------
- Fix job saving
1.0.0
-----------
- Rails 5 support (thanks to @adamico)
- Possibility to schedule jobs with arguments (thanks to @preisanalytics)
- Added :within option to run only within given time interval (thanks to @lhz)
- daemon gem support (thanks to @preisanalytics) https://github.com/plashchynski/crono/pull/37
- Support multiple nodes (thanks to @Natural-Intelligence)
- Fixed DB connection pool issue (thanks to @ChandravatiSG)
0.9.1
-----------
- Add ability to define minimal time between job executions to support multiple corno nodes, so two different nodes will not execute the same job
0.8.9
-----------
- Initial release!
- We moved Web UI to materializecss.com CSS framework
- We moved from CDN to local assets for Web UI
- We show current state of a job in Web UI (thanks to @michaelachrisco) https://github.com/plashchynski/crono/issues/16
- We won't write a pidfile unless daemonized (thanks to @thomasfedb) https://github.com/plashchynski/crono/pull/13
- Fixed `rake crono:clean` task error
- Fixed issue when jobs scheduled at same time exclude each other https://github.com/plashchynski/crono/issues/19
- Fixed issue with a daemon crash due to `time interval must be positive` error
0.5.1
0.8.0
-----------
- Added -e/--environment ENV option to set the daemon rails environment.
- Added `on` (day of week) option to cronotab.rb semantic
- Added job health check and job health indicator to the Web UI
0.7.0
-----------
- Added simple Web UI
0.6.1
-----------
- Persist job state to your database.
0.5.2
-----------
- Fix: Scheduled time now related to the last performing time.
0.6.1
0.5.1
-----------
- Persist job state to your database.
- Added -e/--environment ENV option to set the daemon rails environment.
0.5.0
-----------
- Initial release!

24
Gemfile
View File

@@ -1,2 +1,24 @@
source "https://rubygems.org"
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Specify your gem's dependencies in crono.gemspec.
gemspec
group :development, :test do
# gem 'activerecord'
# gem 'actionpack' # action_controller, action_view
# gem 'sprockets'
gem 'byebug'
gem 'combustion', '~> 1.3'
gem 'daemons'
gem 'rack-test'
gem 'rake', '>= 10.0'
gem 'rspec', '>= 3.0'
gem 'rspec-rails', '>= 4.0'
gem 'sqlite3'
gem 'timecop', '>= 0.7'
end
# To use a debugger
# gem 'byebug', group: [:development, :test]

View File

@@ -1,65 +1,259 @@
PATH
remote: .
specs:
crono (0.6.1)
activejob (~> 4.0)
activerecord (~> 4.0)
activesupport (~> 4.0)
crono (2.1.0)
rails (>= 5.2.8)
GEM
remote: https://rubygems.org/
specs:
activejob (4.2.0)
activesupport (= 4.2.0)
globalid (>= 0.3.0)
activemodel (4.2.0)
activesupport (= 4.2.0)
actioncable (7.2.0)
actionpack (= 7.2.0)
activesupport (= 7.2.0)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.2.0)
actionpack (= 7.2.0)
activejob (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
mail (>= 2.8.0)
actionmailer (7.2.0)
actionpack (= 7.2.0)
actionview (= 7.2.0)
activejob (= 7.2.0)
activesupport (= 7.2.0)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.2.0)
actionview (= 7.2.0)
activesupport (= 7.2.0)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (7.2.0)
actionpack (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.2.0)
activesupport (= 7.2.0)
builder (~> 3.1)
activerecord (4.2.0)
activemodel (= 4.2.0)
activesupport (= 4.2.0)
arel (~> 6.0)
activesupport (4.2.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arel (6.0.0)
builder (3.2.2)
diff-lcs (1.2.5)
globalid (0.3.3)
activesupport (>= 4.1.0)
i18n (0.7.0)
json (1.8.2)
minitest (5.5.1)
rake (10.4.2)
rspec (3.2.0)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.1)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.0)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.2.0)
activesupport (= 7.2.0)
globalid (>= 0.3.6)
activemodel (7.2.0)
activesupport (= 7.2.0)
activerecord (7.2.0)
activemodel (= 7.2.0)
activesupport (= 7.2.0)
timeout (>= 0.4.0)
activestorage (7.2.0)
actionpack (= 7.2.0)
activejob (= 7.2.0)
activerecord (= 7.2.0)
activesupport (= 7.2.0)
marcel (~> 1.0)
activesupport (7.2.0)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
base64 (0.2.0)
bigdecimal (3.1.8)
builder (3.3.0)
byebug (11.1.3)
combustion (1.3.6)
activesupport (>= 3.0.0)
railties (>= 3.0.0)
thor (>= 0.14.6)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crass (1.0.6)
daemons (1.4.1)
date (3.3.4)
diff-lcs (1.5.1)
drb (2.2.1)
erubi (1.13.0)
globalid (1.2.1)
activesupport (>= 6.1)
haml (5.2.2)
temple (>= 0.8.0)
tilt
i18n (1.14.5)
concurrent-ruby (~> 1.0)
io-console (0.7.2)
irb (1.14.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
logger (1.6.0)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.4)
mini_mime (1.1.5)
minitest (5.25.1)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
net-imap (0.4.14)
date
net-protocol
net-pop (0.1.2)
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
propshaft (0.9.1)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.1.2)
stringio
racc (1.8.1)
rack (2.2.9)
rack-protection (2.2.0)
rack
rack-session (1.0.2)
rack (< 3)
rack-test (2.1.0)
rack (>= 1.3)
rackup (1.0.0)
rack (< 3)
webrick
rails (7.2.0)
actioncable (= 7.2.0)
actionmailbox (= 7.2.0)
actionmailer (= 7.2.0)
actionpack (= 7.2.0)
actiontext (= 7.2.0)
actionview (= 7.2.0)
activejob (= 7.2.0)
activemodel (= 7.2.0)
activerecord (= 7.2.0)
activestorage (= 7.2.0)
activesupport (= 7.2.0)
bundler (>= 1.15.0)
railties (= 7.2.0)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.2.0)
actionpack (= 7.2.0)
activesupport (= 7.2.0)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rake (13.2.1)
rdoc (6.7.0)
psych (>= 4.0.0)
reline (0.5.9)
io-console (~> 0.5)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-mocks (3.2.1)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-support (3.2.2)
sqlite3 (1.3.10)
thread_safe (0.3.4)
timecop (0.7.3)
tzinfo (1.2.2)
thread_safe (~> 0.1)
rspec-support (~> 3.13.0)
rspec-rails (6.1.4)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
ruby2_keywords (0.0.5)
securerandom (0.3.1)
sinatra (2.2.0)
mustermann (~> 1.0)
rack (~> 2.2)
rack-protection (= 2.2.0)
tilt (~> 2.0)
sqlite3 (2.0.4-arm64-darwin)
sqlite3 (2.0.4-x86_64-darwin)
sqlite3 (2.0.4-x86_64-linux-gnu)
stringio (3.1.1)
temple (0.8.2)
thor (1.3.1)
tilt (2.0.10)
timecop (0.9.5)
timeout (0.4.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
useragent (0.16.10)
webrick (1.8.1)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
zeitwerk (2.6.17)
PLATFORMS
ruby
arm64-darwin-21
arm64-darwin-23
arm64-darwin-24
x86_64-darwin-21
x86_64-darwin-22
x86_64-linux
DEPENDENCIES
bundler (>= 1.0.0)
bundler (>= 2)
byebug
combustion (~> 1.3)
crono!
rake (~> 10.0)
rspec (~> 3.0)
daemons
haml
propshaft
rack-test
rake (>= 10.0)
rspec (>= 3.0)
rspec-rails (>= 4.0)
sinatra
sqlite3
timecop (~> 0.7)
timecop (>= 0.7)
BUNDLED WITH
2.4.4

View File

@@ -1,4 +1,4 @@
Apache License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -186,7 +186,7 @@ Apache License
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Dzmitry Plashchynski <plashchynski@gmail.com>
Copyright 2022 Dzmitry Plashchynski
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -199,4 +199,3 @@ Apache License
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

2
NOTICE
View File

@@ -1,2 +1,2 @@
Copyright 2015 Dzmitry Plashchynski <plashchynski@gmail.com>
Copyright 2022 Dzmitry Plashchynski <plashchynski@gmail.com>
Licensed under the Apache License, Version 2.0

227
README.md
View File

@@ -1,30 +1,23 @@
Crono — Job scheduler for Rails
Job scheduler for Rails
------------------------
[![Gem Version](https://badge.fury.io/rb/crono.svg)](http://badge.fury.io/rb/crono)
[![Build Status](https://travis-ci.org/plashchynski/crono.svg?branch=master)](https://travis-ci.org/plashchynski/crono)
[![Code Climate](https://codeclimate.com/github/plashchynski/crono/badges/gpa.svg)](https://codeclimate.com/github/plashchynski/crono)
[![security](https://hakiri.io/github/plashchynski/crono/master.svg)](https://hakiri.io/github/plashchynski/crono/master)
[![Join the chat at https://gitter.im/plashchynski/crono](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/plashchynski/crono?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Crono is a time-based background job scheduler daemon (just like Cron) for Ruby on Rails.
## The Purpose
## The Idea
Currently, there is no such thing as Ruby Cron for Rails. Well, there's [Whenever](https://github.com/javan/whenever) but it works on top of Unix Cron, so you can't manage it from Ruby. Crono is pure Ruby. It doesn't use Unix Cron and other platform-dependent things. So you can use it on all platforms supported by Ruby. It persists job states to your database using Active Record. You have full control of jobs performing process. It's Ruby, so you can understand and modify it to fit your needs.
Currently there is no such thing as Cron in Ruby for Rails. Well, there's [Whenever](https://github.com/javan/whenever) but it works on top of Unix Cron, so you have no total control of it from Ruby. Crono is pure Ruby. It doesn't use Unix Cron and other platform-dependent things. So you can use it on all platforms supported by Ruby. It persists job state to your database using Active Record. You have full control of jobs performing process. You have Ruby code, so you can understand and modify it to fit your needs.
## Requirements
Tested with latest MRI Ruby (2.2, 2.1 and 2.0) and Rails 3.2+
Other versions are untested but might work fine.
![Web UI](https://github.com/plashchynski/crono/raw/main/examples/crono_web_ui.png)
## Installation
Add the following line to your application's Gemfile:
gem 'crono'
```ruby
gem 'crono'
```
Run the `bundle` command to install it.
After you install Crono, you can run the generator:
@@ -38,76 +31,202 @@ Run the migration:
Now you are ready to move forward to create a job and schedule it.
### Compatibility
* **Crono v1.1.2** and older are compatible with Ruby 2.7.x and older
* **Crono v2.0.0** and newer are compatible with Ruby 2.7.x and _newer_
## Usage
#### Create Job
### The basic usage
Crono can use Active Job jobs from `app/jobs/`. The only requirements is that the `perform` method should take no arguments.
You can specify a simple job by editing ```config/cronotab.rb```:
Here's an example of a test job:
```ruby
# config/cronotab.rb
class TestJob
def perform
puts 'Test!'
end
end
# app/jobs/test_job.rb
class TestJob < ActiveJob::Base
def perform
# put you scheduled code here
# Comments.deleted.clean_up...
end
end
Crono.perform(TestJob).every 5.seconds
```
The ActiveJob jobs is convenient because you can use one job in both periodic and enqueued ways. But Active Job is not required. Any class can be used as a crono job if it implements a method `perform` without arguments:
Then, run a crono process:
class TestJob # This is not an Active Job job, but pretty legal Crono job.
def perform
# put you scheduled code here
# Comments.deleted.clean_up...
end
end
```bundle exec crono -e development```
### Job Schedule
#### Job Schedule
Schedule list is defined in the file `config/cronotab.rb`, that created using `rake crono:install`. The semantic is pretty straightforward:
The schedule described in the configuration file `config/cronotab.rb`, that created using `crono:install` or manually. The semantic is pretty straightforward:
```ruby
# config/cronotab.rb
Crono.perform(TestJob).every 2.days, at: {hour: 15, min: 30}
Crono.perform(TestJob).every 1.week, on: :monday, at: "15:30"
```
# config/cronotab.rb
Crono.perform(TestJob).every 2.days, at: "15:30"
You can schedule one job a few times if you want the job to be performed a few times a day or a week:
You can schedule one job a few times, if you want a job to be performed a few times a day:
Crono.perform(TestJob).every 1.day, at: "00:00"
Crono.perform(TestJob).every 1.day, at: "12:00"
```ruby
Crono.perform(TestJob).every 1.week, on: :monday
Crono.perform(TestJob).every 1.week, on: :thursday
```
The `at` can be a Hash:
Crono.perform(TestJob).every 1.day, at: {hour: 12, min: 15}
#### Run daemon
To run Crono daemon, in your Rails project root directory:
bundle exec crono RAILS_ENV=development
crono usage:
```ruby
Crono.perform(TestJob).every 1.day, at: {hour: 12, min: 15}
```
Usage: crono [options]
You can schedule a job with arguments, which can contain objects that can be serialized using JSON.generate
```ruby
Crono.perform(TestJob, 'some', 'args').every 1.day, at: {hour: 12, min: 15}
```
You can set some options that not passed to the job but affect how the job will be treated by Crono. For example, you can set to truncate job logs (which stored in the database) to a certain number of records:
```ruby
Crono.perform(TestJob).with_options(truncate_log: 100).every 1.week, on: :monday
```
### Job classes
Crono can use Active Job jobs from `app/jobs/`. Here's an example of a job:
```ruby
# app/jobs/test_job.rb
class TestJob < ActiveJob::Base
def perform(options)
# put you scheduled code here
# Comments.deleted.clean_up...
end
end
```
The ActiveJob jobs are convenient because you can use one job in both periodic and enqueued ways. But Active Job is not required. Any class can be used as a crono job if it implements a method `perform`:
```ruby
class TestJob # This is not an Active Job job, but pretty legal Crono job.
def perform(*args)
# put you scheduled code here
# Comments.deleted.clean_up...
end
end
```
### Run rake tasks
Here's an example of a Rake Task within a job:
```ruby
# config/cronotab.rb
require 'rake'
Rails.app_class.load_tasks
class Test
def perform
Rake::Task['crono:hello'].execute
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_
### Run crono
Run crono in your Rails project root directory:
bundle exec crono -e development
Usage:
```
Usage: crono [options] [start|stop|restart|run]
-C, --cronotab PATH Path to cronotab file (Default: config/cronotab.rb)
-L, --logfile PATH Path to writable logfile (Default: log/crono.log)
-P, --pidfile PATH Path to pidfile (Default: tmp/pids/crono.pid)
-d, --[no-]daemonize Daemonize process (Default: false)
-P, --pidfile PATH Deprecated! use --piddir with --process_name; Path to pidfile (Default: )
-D, --piddir PATH Path to piddir (Default: tmp/pids)
-N, --process_name NAME Name of the process (Default: crono)
-m, --monitor Start monitor process for a deamon (Default false)
-e, --environment ENV Application environment (Default: development)
```
### Run as a daemon
To run Crono as a daemon, please add to your Gemfile:
```ruby
gem 'daemons'
```
Then:
bundle install; bundle exec crono start RAILS_ENV=development
There are "start", "stop", and "restart" commands.
## Web UI
Crono can display the current state of Crono jobs.
Add the following to your `config/routes.rb`:
```ruby
Rails.application.routes.draw do
mount Crono::Engine, at: '/crono'
...
```
Access management and other questions described in the [wiki](https://github.com/plashchynski/crono/wiki/Web-UI).
## Known issues
For Rails 5, in case of the errors:
```
`require': cannot load such file -- rack/showexceptions (LoadError)
```
See the related issue [#52](https://github.com/plashchynski/crono/issues/52)
## Capistrano
Use the `capistrano-crono` gem ([github](https://github.com/plashchynski/capistrano-crono/)).
## Development
### Running tests
To run the tests, you need to have a database. You can use the default SQLite database:
bundle exec rspec
### Publishing
To publish a new version, you need to update the version number in `lib/crono/version.rb` and then run:
bundle exec rake release
## Support
Feel free to create [issues](https://github.com/plashchynski/crono/issues)
Feel free to create an [issues](https://github.com/plashchynski/crono/issues)
## License
Please see [LICENSE](https://github.com/plashchynski/crono/blob/master/LICENSE) for licensing details.
Please see [LICENSE](LICENSE) for licensing details.

View File

@@ -1,7 +1,5 @@
require 'bundler'
Bundler::GemHelper.install_tasks
require 'bundler/setup'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new('spec')
load 'rails/tasks/statistics.rake'
task default: :spec
require 'bundler/gem_tasks'

11
SECURITY.md Normal file
View File

@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
## Reporting a Vulnerability
You are welcome to report security information to [plashchynski@gmail.com](mailto:plashchynski@gmail.com?subject=[GitHub]%20Security)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
nav {
position: relative;
z-index: 2;
}
nav .brand-logo {
font-size: 1.75em;
letter-spacing: -0.05em;
}
main {
position: relative;
z-index: 1;
padding: 20px;
}
main .page-footer {
margin: 0 -20px -20px -20px;
}
.page-footer > .footer-copyright {
padding: 20px;
}
.responsive-overflow {
overflow-x: auto;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
module Crono
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
end

View File

@@ -0,0 +1,11 @@
module Crono
class JobsController < ApplicationController
def index
@jobs = Crono::CronoJob.all
end
def show
@job = Crono::CronoJob.find(params[:id])
end
end
end

View File

@@ -0,0 +1,5 @@
module Crono
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
end

View File

@@ -0,0 +1,11 @@
module Crono
# Crono::CronoJob is a ActiveRecord model to store job state
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

@@ -0,0 +1,50 @@
<header>
<h4>Running Jobs</h4>
</header>
<div class="responsive-overflow">
<table id="job_list">
<tr>
<th>Job</th>
<th>Last performed at</th>
<th>Status</th>
<th></th>
</tr>
<% @jobs.each do |job| %>
<tr>
<td>
<%= job.job_id %>
</td>
<td>
<%= job.last_performed_at || 'Never performed yet' %>
</td>
<td>
<% if job.last_performed_at.nil? %>
<span class="grey-text darken-3" title="This job has never been performed yet.">
◷ Pending
</span>
<% elsif job.healthy %>
<a href="<%= job_path(job) %>">
<span class="green-text darken-3" title="This job was performed successfully.">
✔ Success
</span>
</a>
<% else %>
<a href="<%= job_path(job) %>">
<span class="red-text" title="There were problems with this job. Follow the link to check the log.">
⚠ Error
</span>
</a>
<% end %>
</td>
<td>
<% if job.last_performed_at %>
<a href="<%= job_path(job) %>" class="right">
Log
</a>
<% end %>
</td>
</tr>
<% end %>
</table>
</div>

View File

@@ -0,0 +1,16 @@
<header class="blue-grey lighten-4 grey-text text-darken-4">
<a href="<%= root_url %>">
Back to Home
</a>
<h4>Last log of <i><%= @job.job_id %></i>:</h4>
</header>
<% if @job.healthy == false %>
<div class="row red-text">
<div class="s12 center-align">
An error occurs during the last execution of this job.
Check the log below for details.
</div>
</div>
<% end %>
<pre class="responsive-overflow"><%= @job.log %></pre>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>Crono Dashboard</title>
<%= stylesheet_link_tag 'crono/materialize.min.css', media: "all" %>
<%= stylesheet_link_tag 'crono/application', media: "all" %>
<%= csrf_meta_tags %>
</head>
<body class="blue-grey darken-2">
<nav class="blue-grey white-text" role="navigation">
<a class="brand-logo center" href="<%= root_path %>">
<b>Crono</b> Dashboard
</a>
</nav>
<main class="container blue-grey lighten-4">
<%= yield %>
<footer class="page-footer blue-grey darken-2">
<div class="footer-copyright blue-grey">
Crono v<%= Crono::VERSION %>
<a class="right" href="https://github.com/plashchynski/crono" target="_blank">Documentation</a>
</div>
</footer>
</main>
</body>
</html>

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require "crono/cli"
require 'crono/cli'
begin
Crono::CLI.instance.run

24
bin/rails Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
ENGINE_ROOT = File.expand_path('..', __dir__)
ENGINE_PATH = File.expand_path('../lib/crono/engine', __dir__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails'
# Pick the frameworks you want:
require 'active_model/railtie'
require 'active_job/railtie'
require 'active_record/railtie'
require 'active_storage/engine'
require 'action_controller/railtie'
require 'action_mailer/railtie'
require 'action_view/railtie'
require 'action_cable/engine'
# require "sprockets/railtie"
# require "rails/test_unit/railtie"
require 'rails/engine/commands'

9
config.ru Normal file
View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
require "rubygems"
require "bundler"
Bundler.require :default, :development
Combustion.initialize! :all
run Combustion::Application

4
config/routes.rb Normal file
View File

@@ -0,0 +1,4 @@
Crono::Engine.routes.draw do
resources :jobs
root 'jobs#index'
end

View File

@@ -1,28 +1,33 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/crono/version', __FILE__)
$:.push File.expand_path("../lib", __FILE__)
require_relative 'lib/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"
s.name = 'crono'
s.version = Crono::VERSION
s.authors = ['Dzmitry Plashchynski']
s.email = ['plashchynski@gmail.com']
s.required_rubygems_version = ">= 1.3.6"
s.rubyforge_project = "crono"
s.summary = 'Job scheduler for Rails'
s.description = 'A time-based background job scheduler daemon (just like Cron) for Rails'
s.homepage = 'https://github.com/plashchynski/crono'
s.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.files = Dir['{app,config,db,lib}/**/*', 'LICENSE', 'Rakefile', 'README.rdoc']
s.test_files = Dir['spec/**/*']
s.executables = ['crono']
s.require_paths = ["lib"]
s.files = `git ls-files`.split("\n")
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
s.require_path = 'lib'
s.add_dependency 'rails', '>= 5.2.8'
s.add_development_dependency 'rake', '>= 13.0.1'
s.add_development_dependency 'bundler', '>= 2'
s.add_development_dependency 'rspec', '>= 3.10'
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'
s.add_development_dependency 'daemons'
s.add_development_dependency 'propshaft'
end

BIN
examples/crono_web_ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -1,15 +1,14 @@
# cronotab.rb Crono configuration example file
# cronotab.rb - Crono configuration file
#
# Here you can specify periodic jobs and their schedule.
# You can specify a periodic job as a ActiveJob class in `app/jobs/`
# Actually you can use any class. The only requirement is that
# the class should implement a method `perform` without arguments.
# Here you can specify periodic jobs and schedule.
# You can use ActiveJob's jobs from `app/jobs/`
# You can use any class. The only requirement is that
# class should have a method `perform` without arguments.
#
class TestJob
def perform
puts "Test!"
puts 'Test!'
end
end
Crono.perform(TestJob).every 5.second
Crono.perform(TestJob).every 2.days, at: '15:30'

View File

@@ -1,12 +1,18 @@
# Crono main module
module Crono
end
require "active_support/all"
require "crono/version.rb"
require "crono/logging.rb"
require "crono/period.rb"
require "crono/job.rb"
require "crono/scheduler.rb"
require "crono/config.rb"
require "crono/performer_proxy.rb"
require "crono/orm/active_record/crono_job.rb"
require 'rails'
require 'active_support/all'
require 'crono/version'
require 'crono/engine'
require 'crono/logging'
require 'crono/period'
require 'crono/time_of_day'
require 'crono/interval'
require 'crono/job'
require 'crono/scheduler'
require 'crono/config'
require 'crono/performer_proxy'
require 'crono/cronotab'
require 'crono/railtie' if defined?(Rails)

View File

@@ -1,13 +1,16 @@
Thread.abort_on_exception = true
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
include Logging
COMMANDS = %w(start stop restart run zap reload status)
attr_accessor :config
def initialize
@@ -17,35 +20,43 @@ module Crono
def run
parse_options(ARGV)
parse_command(ARGV)
if config.daemonize
set_log_to(config.logfile)
daemonize
else
set_log_to(STDOUT)
end
setup_log
write_pid
write_pid if config.daemonize
load_rails
Cronotab.process(File.expand_path(config.cronotab))
print_banner
check_jobs
start_working_loop
end
private
def daemonize
::Process.daemon(true, true)
[$stdout, $stderr].each do |io|
File.open(config.logfile, 'ab') { |f| io.reopen(f) }
io.sync = true
unless have_jobs?
logger.error "You have no jobs in you cronotab file #{config.cronotab}"
return
end
$stdin.reopen("/dev/null")
if config.daemonize
start_working_loop_in_daemon
else
start_working_loop
end
end
private
def have_jobs?
Crono.scheduler.jobs.present?
end
def setup_log
if config.daemonize
self.logfile = config.logfile
else
self.logfile = STDOUT
end
end
def write_pid
return unless config.pidfile
pidfile = File.expand_path(config.pidfile)
File.write(pidfile, ::Process.pid)
end
@@ -54,37 +65,56 @@ module Crono
logger.info "Loading Crono #{Crono::VERSION}"
logger.info "Running in #{RUBY_DESCRIPTION}"
logger.info "Jobs:"
logger.info 'Jobs:'
Crono.scheduler.jobs.each do |job|
logger.info job.description
logger.info "'#{job.performer}' with rule '#{job.period.description}'"\
" next time will perform at #{job.next}"
end
end
def load_rails
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = config.environment
require 'rails'
require File.expand_path("config/environment.rb")
require File.expand_path('config/environment.rb')
::Rails.application.eager_load!
require File.expand_path(config.cronotab)
end
def check_jobs
if Crono.scheduler.jobs.empty?
logger.error "You have no jobs defined in you cronotab file #{config.cronotab}"
def start_working_loop_in_daemon
unless ENV['RAILS_ENV'] == 'test'
begin
require 'daemons'
rescue LoadError
raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
end
end
Daemons.run_proc(config.process_name, dir: config.piddir, dir_mode: :normal, monitor: config.monitor, ARGV: @argv) do |*_argv|
Dir.chdir(root)
Crono.logger = Logger.new(config.logfile)
start_working_loop
end
end
def root
@root ||= rails_root_defined? ? ::Rails.root : DIR_PWD
end
def rails_root_defined?
defined?(::Rails.root)
end
def start_working_loop
Thread.abort_on_exception = true
while job = Crono.scheduler.next do
sleep(job.next - Time.now)
job.perform
loop do
next_time, jobs = Crono.scheduler.next_jobs
now = Time.zone.now
sleep(next_time - now) if next_time > now
jobs.each(&:perform)
end
end
def parse_options(argv)
OptionParser.new do |opts|
opts.banner = "Usage: crono [options]"
@argv = OptionParser.new do |opts|
opts.banner = "Usage: crono [options] [start|stop|restart|run]"
opts.on("-C", "--cronotab PATH", "Path to cronotab file (Default: #{config.cronotab})") do |cronotab|
config.cronotab = cronotab
@@ -94,12 +124,20 @@ module Crono
config.logfile = logfile
end
opts.on("-P", "--pidfile PATH", "Path to pidfile (Default: #{config.pidfile})") do |pidfile|
opts.on("-P", "--pidfile PATH", "Deprecated! use --piddir with --process_name; Path to pidfile (Default: #{config.pidfile})") do |pidfile|
config.pidfile = pidfile
end
opts.on("-d", "--[no-]daemonize", "Daemonize process (Default: #{config.daemonize})") do |daemonize|
config.daemonize = daemonize
opts.on("-D", "--piddir PATH", "Path to piddir (Default: #{config.piddir})") do |piddir|
config.piddir = piddir
end
opts.on("-N", "--process_name NAME", "Name of the process (Default: #{config.process_name})") do |process_name|
config.process_name = process_name
end
opts.on("-m", "--monitor", "Start monitor process for a deamon (Default #{config.monitor})") do
config.monitor = true
end
opts.on '-e', '--environment ENV', "Application environment (Default: #{config.environment})" do |env|
@@ -107,5 +145,12 @@ module Crono
end
end.parse!(argv)
end
def parse_command(argv)
if COMMANDS.include? argv[0]
config.daemonize = true
end
end
end
end

View File

@@ -1,21 +1,33 @@
module Crono
# Crono::Config stores Crono configuration
class Config
CRONOTAB = "config/cronotab.rb"
LOGFILE = "log/crono.log"
PIDFILE = "tmp/pids/crono.pid"
CRONOTAB = 'config/cronotab.rb'
LOGFILE = 'log/crono.log'
PIDFILE = 'tmp/pids/crono.pid'
PIDDIR = 'tmp/pids'
PROCESS_NAME = 'crono'
attr_accessor :cronotab
attr_accessor :logfile
attr_accessor :pidfile
attr_accessor :daemonize
attr_accessor :environment
attr_accessor :cronotab, :logfile, :pidfile, :piddir, :process_name,
:monitor, :daemonize, :environment
def initialize
self.cronotab = CRONOTAB
self.logfile = LOGFILE
self.pidfile = PIDFILE
self.piddir = PIDDIR
self.process_name = PROCESS_NAME
self.daemonize = false
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development"
self.monitor = false
self.environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
def pidfile=(pidfile)
@pidfile = pidfile
self.process_name = Pathname.new(pidfile).basename(".*").to_s
self.piddir = Pathname.new(pidfile).dirname.to_s
end
def pidfile
@pidfile
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

19
lib/crono/engine.rb Normal file
View File

@@ -0,0 +1,19 @@
module Crono
class Engine < ::Rails::Engine
isolate_namespace Crono
initializer 'crono.assets.precompile' do |app|
if app.config.respond_to?(:assets)
app.config.assets.precompile += %w( crono/application.css crono/materialize.min.css )
else
fail "Crono requires either Propshaft or Sprockets to be installed."
end
end
config.generators do |g|
g.test_framework :rspec
g.assets false
g.helper false
end
end
end

43
lib/crono/interval.rb Normal file
View File

@@ -0,0 +1,43 @@
module Crono
# Interval describes a period between two specific times of day
class Interval
attr_accessor :from, :to
def self.parse(value)
from_to =
case value
when Array then value
when Hash then value.values_at(:from, :to)
when String then value.split('-')
else
fail "Unknown interval format: #{value.inspect}"
end
from, to = from_to.map { |v| TimeOfDay.parse(v) }
new from, to
end
def initialize(from, to)
@from, @to = from, to
end
def within?(value)
tod = ((value.is_a? TimeOfDay) ? value : TimeOfDay.parse(value))
if @from <= @to
tod >= @from && tod < @to
else
tod >= @from || tod < @to
end
end
def next_within(time, period)
begin
time = period.since(time)
end until within? TimeOfDay.parse(time)
time
end
def to_s
"#{@from}-#{@to}"
end
end
end

View File

@@ -1,18 +1,28 @@
require 'stringio'
require 'logger'
module Crono
# Crono::Job represents a Crono job
class Job
include Logging
attr_accessor :performer
attr_accessor :period
attr_accessor :last_performed_at
attr_accessor :performer, :period, :job_args, :last_performed_at, :job_options,
:next_performed_at, :job_log, :job_logger, :healthy, :execution_interval
def initialize(performer, period)
def initialize(performer, period, job_args = nil, job_options = nil)
self.execution_interval = 0.minutes
self.performer, self.period = performer, period
self.job_args = JSON.generate(job_args) if job_args.present?
self.job_log = StringIO.new
self.job_logger = Logger.new(job_log)
self.job_options = job_options || {}
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.zone.now
end
def description
@@ -24,26 +34,110 @@ module Crono
end
def perform
logger.info "Perform #{performer}"
self.last_performed_at = Time.now
save
Thread.new do
performer.new.perform
logger.info "Finished #{performer} in %.2f seconds" % (Time.now - last_performed_at)
end
return Thread.new {} if perform_before_interval?
log "Perform #{performer}"
self.last_performed_at = Time.zone.now
self.next_performed_at = period.next(since: last_performed_at)
Thread.new { perform_job }
end
def save
model.update(last_performed_at: last_performed_at)
@semaphore.synchronize do
update_model
clear_job_log
ActiveRecord::Base.connection_handler.clear_active_connections!
end
end
def load
self.last_performed_at = model.last_performed_at
self.next_performed_at = period.next(since: last_performed_at)
end
private
def clear_job_log
job_log.truncate(job_log.rewind)
end
def truncate_log(log)
return log.lines.last(job_options[:truncate_log]).join if job_options[:truncate_log]
return log
end
def update_model
saved_log = model.reload.log || ''
log_to_save = saved_log + job_log.string
log_to_save = truncate_log(log_to_save)
model.update(last_performed_at: last_performed_at, log: log_to_save,
healthy: healthy)
end
def perform_job
if job_args
performer.new.perform(JSON.parse(job_args))
else
performer.new.perform
end
rescue StandardError => e
handle_job_fail(e)
else
handle_job_success
ensure
save
end
def handle_job_fail(exception)
finished_time_sec = format('%.2f', Time.zone.now - last_performed_at)
self.healthy = false
log_error "Finished #{performer} in #{finished_time_sec} seconds"\
" with error: #{exception.message}"
log_error exception.backtrace.join("\n")
end
def handle_job_success
finished_time_sec = format('%.2f', Time.zone.now - last_performed_at)
self.healthy = true
log "Finished #{performer} in #{finished_time_sec} seconds"
end
def log_error(message)
log(message, Logger::ERROR)
end
def log(message, severity = Logger::INFO)
@semaphore.synchronize do
logger.log(severity, message) if logger
job_logger.log severity, message
end
end
private
def model
@model ||= Crono::CronoJob.find_or_create_by(job_id: job_id)
end
def perform_before_interval?
return false if execution_interval == 0.minutes
return true if self.last_performed_at.present? && self.last_performed_at > execution_interval.ago
return true if model.updated_at.present? && model.created_at != model.updated_at && model.updated_at > execution_interval.ago
Crono::CronoJob.transaction do
job_record = Crono::CronoJob.where(job_id: job_id).lock(true).first
return true if job_record.updated_at.present? &&
job_record.updated_at != job_record.created_at &&
job_record.updated_at > execution_interval.ago
job_record.touch
return true unless job_record.save
end
# Means that this node is permit to perform the job.
return false
end
end
end

View File

@@ -1,8 +1,9 @@
module Crono
mattr_accessor :logger
# Crono::Logging is a standart Ruby logger wrapper
module Logging
def set_log_to(logfile)
def logfile=(logfile)
Crono.logger = Logger.new(logfile)
end

View File

@@ -1,8 +0,0 @@
require 'active_record'
module Crono
class CronoJob < ActiveRecord::Base
self.table_name = "crono_jobs"
validates :job_id, presence: true, uniqueness: true
end
end

View File

@@ -1,17 +1,31 @@
module Crono
# Crono::PerformerProxy is a proxy used in cronotab.rb semantic
class PerformerProxy
def initialize(performer, scheduler)
def initialize(performer, scheduler, job_args = nil, job_options = nil)
@performer = performer
@scheduler = scheduler
@job_args = job_args
@job_options = job_options
end
def every(period, *args)
job = Job.new(@performer, Period.new(period, *args))
@scheduler.add_job(job)
def every(period, **options)
@job = Job.new(@performer, Period.new(period, **options), @job_args, @job_options)
@scheduler.add_job(@job)
self
end
def once_per(execution_interval)
@job.execution_interval = execution_interval if @job
self
end
def with_options(options)
@job_options = options
self
end
end
def self.perform(performer)
PerformerProxy.new(performer, Crono.scheduler)
def self.perform(performer, *job_args)
PerformerProxy.new(performer, Crono.scheduler, *job_args)
end
end

View File

@@ -1,31 +1,85 @@
module Crono
# Period describe frequency of jobs
class Period
def initialize(period, at: nil)
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday,
:sunday]
def initialize(period, at: nil, on: nil, within: nil)
@period = period
@at_hour, @at_min = parse_at(at) if at
@interval = Interval.parse(within) if within
@on = parse_on(on) if on
end
def next(since: nil)
since ||= Time.now
@period.since(since).change({hour: @at_hour, min: @at_min}.compact)
if @interval
if since
@next = @interval.next_within(since, @period)
else
return initial_next if @interval.within?(initial_next)
@next = @interval.next_within(initial_next, @period)
end
else
return initial_next unless since
@next = @period.since(since)
end
@next = @next.beginning_of_week.advance(days: @on) if @on
@next = @next.change(time_atts)
return @next if @next.future?
Time.zone.now
end
def description
desc = "every #{@period.inspect}"
desc += " at %.2i:%.2i" % [@at_hour, @at_min] if @at_hour && @at_min
desc += " between #{@interval.from} and #{@interval.to} UTC" if @interval
desc += format(' at %.2i:%.2i', @at_hour, @at_min) if @at_hour && @at_min
desc += " on #{DAYS[@on].capitalize}" if @on
desc
end
private
def initial_next
next_time = initial_day.change(time_atts)
return next_time if next_time.future?
@period.from_now.change(time_atts)
end
def initial_day
return Time.zone.now unless @on
day = Time.zone.now.beginning_of_week.advance(days: @on)
day = day.change(time_atts)
return day if day.future?
@period.from_now.beginning_of_week.advance(days: @on)
end
def parse_on(on)
day_number = DAYS.index(on)
fail "Wrong 'on' day" unless day_number
fail "period should be at least 1 week to use 'on'" if @period < 1.week
day_number
end
def parse_at(at)
if @period < 1.day && (at.is_a? String || at[:hour])
fail "period should be at least 1 day to use 'at' with specified hour"
end
case at
when String
time = Time.parse(at)
time = Time.zone.parse(at)
return time.hour, time.min
when Hash
return at[:hour], at[:min]
else
raise "Unknown 'at' format"
fail "Unknown 'at' format"
end
end
def time_atts
atts = { hour: @at_hour, min: @at_min }
atts.respond_to?(:compact) ? atts.compact : atts.select { |_, value| !value.nil? }
end
end
end

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

@@ -1,4 +1,5 @@
module Crono
# Scheduler is a container for job list and queue
class Scheduler
attr_accessor :jobs
@@ -11,13 +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

36
lib/crono/time_of_day.rb Normal file
View File

@@ -0,0 +1,36 @@
module Crono
# TimeOfDay describes a certain hour and minute (on any day)
class TimeOfDay
include Comparable
attr_accessor :hour, :min
def self.parse(value)
time =
case value
when String then Time.zone.parse(value).utc
when Hash then Time.zone.now.change(value).utc
when Time then value.utc
else
fail "Unknown TimeOfDay format: #{value.inspect}"
end
new time.hour, time.min
end
def initialize(hour, min)
@hour, @min = hour, min
end
def to_i
@hour * 60 + @min
end
def to_s
'%02d:%02d' % [@hour, @min]
end
def <=>(other)
to_i <=> other.to_i
end
end
end

View File

@@ -1,3 +1,3 @@
module Crono
VERSION = '0.6.1'
VERSION = '2.1.0'
end

View File

@@ -4,6 +4,7 @@ require 'rails/generators/active_record'
module Crono
module Generators
# rails generate crono:install
class InstallGenerator < ::Rails::Generators::Base
include Rails::Generators::Migration
@@ -11,15 +12,27 @@ module Crono
ActiveRecord::Generators::Base.next_migration_number(path)
end
desc "Installs crono and generates the necessary configuration files"
source_root File.expand_path("../templates", __FILE__)
desc 'Installs crono and generates the necessary configuration files'
source_root File.expand_path('../templates', __FILE__)
def copy_config
template 'cronotab.rb.erb', 'config/cronotab.rb'
end
def create_migrations
migration_template 'migrations/create_crono_jobs.rb', 'db/migrate/create_crono_jobs.rb'
migration_template 'migrations/create_crono_jobs.rb',
'db/migrate/create_crono_jobs.rb',
migration_version: migration_version
end
def rails5?
Rails.version.start_with? '5'
end
def migration_version
if rails5?
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
end
end
end

View File

@@ -1,9 +1,15 @@
# cronotab.rb — Crono configuration file
#
# Here you can specify periodic jobs and their schedule.
# You can specify a periodic job as a ActiveJob class in `app/jobs/`
# Actually you can use any class. The only requirement is that
# the class should implement a method `perform` without arguments.
# Here you can specify periodic jobs and schedule.
# You can use ActiveJob's jobs from `app/jobs/`
# You can use any class. The only requirement is that
# class should have a method `perform` without arguments.
#
# Crono.perform(TestJob).every 2.days, at: "15:30"
# class TestJob
# def perform
# puts 'Test!'
# end
# end
#
# Crono.perform(TestJob).every 2.days, at: '15:30'
#

View File

@@ -1,15 +1,12 @@
class CreateCronoJobs < ActiveRecord::Migration
def self.up
class CreateCronoJobs < ActiveRecord::Migration[6.1]
def change
create_table :crono_jobs do |t|
t.string :job_id, null: false
t.text :log
t.text :log, limit: 1073741823 # LONGTEXT for MySQL
t.datetime :last_performed_at
t.timestamps
t.boolean :healthy
t.timestamps null: false
end
add_index :crono_jobs, [:job_id], unique: true
end
def self.down
drop_table :crono_jobs
end
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,51 +1,110 @@
require "spec_helper"
require 'spec_helper'
require 'crono/cli'
class TestJob
def perform;end
end
describe Crono::CLI do
let(:cli) { Crono::CLI.instance }
describe "#run" do
it "should try to initialize rails with #load_rails and start working loop" do
describe '#run' do
it 'should initialize rails with #load_rails and start working loop' do
expect(cli).to receive(:load_rails)
expect(cli).to receive(:have_jobs?).and_return(true)
expect(cli).to receive(:start_working_loop)
expect(cli).to receive(:parse_options)
expect(cli).to receive(:write_pid)
expect(cli).to receive(:parse_command)
expect(cli).not_to receive(:write_pid)
expect(Crono::Cronotab).to receive(:process)
cli.run
end
context 'should run as daemon' do
before { cli.config.daemonize = true }
it 'should initialize rails with #load_rails and start working loop' do
expect(cli).to receive(:load_rails)
expect(cli).to receive(:have_jobs?).and_return(true)
expect(cli).to receive(:start_working_loop_in_daemon)
expect(cli).to receive(:parse_options)
expect(cli).to receive(:parse_command)
expect(cli).to receive(:setup_log)
expect(cli).to receive(:write_pid)
expect(Crono::Cronotab).to receive(:process)
cli.run
end
end
end
describe "#start_working_loop" do
it "should start working loop"
describe '#parse_options' do
it 'should set cronotab' do
cli.send(:parse_options, ['--cronotab', '/tmp/cronotab.rb'])
expect(cli.config.cronotab).to be_eql '/tmp/cronotab.rb'
end
it 'should set logfile' do
cli.send(:parse_options, ['--logfile', 'log/crono.log'])
expect(cli.config.logfile).to be_eql 'log/crono.log'
end
it 'should set pidfile' do
cli.send(:parse_options, ['--pidfile', 'tmp/pids/crono.0.log'])
expect(cli.config.pidfile).to be_eql 'tmp/pids/crono.0.log'
end
it 'should set piddir' do
cli.send(:parse_options, ['--piddir', 'tmp/pids'])
expect(cli.config.piddir).to be_eql 'tmp/pids'
end
it 'should set process_name' do
cli.send(:parse_options, ['--process_name', 'crono0'])
expect(cli.config.process_name).to be_eql 'crono0'
end
it 'should set monitor' do
cli.send(:parse_options, ['--monitor'])
expect(cli.config.monitor).to be true
end
it 'should set environment' do
cli.send(:parse_options, ['--environment', 'production'])
expect(cli.config.environment).to be_eql('production')
end
end
describe "#parse_options" do
it "should set cronotab" do
cli.send(:parse_options, ["--cronotab", "/tmp/cronotab.rb"])
expect(cli.config.cronotab).to be_eql "/tmp/cronotab.rb"
end
describe '#parse_command' do
it "should set logfile" do
cli.send(:parse_options, ["--logfile", "log/crono.log"])
expect(cli.config.logfile).to be_eql "log/crono.log"
end
it "should set pidfile" do
cli.send(:parse_options, ["--pidfile", "tmp/pids/crono.0.log"])
expect(cli.config.pidfile).to be_eql "tmp/pids/crono.0.log"
end
it "should set daemonize" do
cli.send(:parse_options, ["--daemonize"])
it 'should set daemonize on start' do
cli.send(:parse_command, ['start'])
expect(cli.config.daemonize).to be true
end
it "should set environment" do
cli.send(:parse_options, ["--environment", "production"])
expect(cli.config.environment).to be_eql("production")
it 'should set daemonize on stop' do
cli.send(:parse_command, ['stop'])
expect(cli.config.daemonize).to be true
end
it 'should set daemonize on restart' do
cli.send(:parse_command, ['restart'])
expect(cli.config.daemonize).to be true
end
it 'should set daemonize on run' do
cli.send(:parse_command, ['run'])
expect(cli.config.daemonize).to be true
end
it 'should set daemonize on zap' do
cli.send(:parse_command, ['zap'])
expect(cli.config.daemonize).to be true
end
it 'should set daemonize on reload' do
cli.send(:parse_command, ['reload'])
expect(cli.config.daemonize).to be true
end
it 'should set daemonize on status' do
cli.send(:parse_command, ['status'])
expect(cli.config.daemonize).to be true
end
end
end

View File

@@ -1,15 +1,47 @@
require "spec_helper"
require 'spec_helper'
describe Crono::Config do
describe "#initialize" do
it "should initialize with default configuration options" do
ENV["RAILS_ENV"] = "test"
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.piddir).to be Crono::Config::PIDDIR
expect(@config.process_name).to be Crono::Config::PROCESS_NAME
expect(@config.daemonize).to be false
expect(@config.environment).to be_eql ENV["RAILS_ENV"]
expect(@config.monitor).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
end
context "explicity configured" do
let(:path) { "foo/bar/pid.pid" }
before { config.pidfile = path }
specify { expect(pidfile).to eq path }
it "trys to set piddir" do
expect(config.piddir).to eq "foo/bar"
end
it "trys to set process_name" do
expect(config.process_name).to eq "pid"
end
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

@@ -0,0 +1,3 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end

View File

@@ -0,0 +1,5 @@
class PagesController < ApplicationController
def index
#
end
end

View File

@@ -0,0 +1 @@
<p>Hello World</p>

View File

@@ -0,0 +1,22 @@
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
require 'crono'
module Dummy
class Application < Rails::Application
config.load_defaults Rails::VERSION::STRING.to_f
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end

View File

@@ -0,0 +1,5 @@
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
require "bundler/setup" if File.exist?(ENV['BUNDLE_GEMFILE'])
$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)

View File

@@ -0,0 +1,3 @@
test:
adapter: sqlite3
database: db/crono_test.sqlite

View File

@@ -0,0 +1,2 @@
# Load the Rails application.
require_relative 'application'

View File

@@ -0,0 +1,3 @@
Rails.application.routes.draw do
root 'pages#index'
end

View File

@@ -0,0 +1,3 @@
test:
service: Disk
root: /Users/chris/Sites/_playground/crono/tmp/storage

View File

@@ -0,0 +1,10 @@
ActiveRecord::Schema.define do
create_table :crono_jobs do |t|
t.string :job_id, null: false
t.text :log, limit: 1_073_741_823 # LONGTEXT for MySQL
t.datetime :last_performed_at
t.boolean :healthy
t.timestamps null: false
end
add_index :crono_jobs, [:job_id], unique: true
end

1
spec/internal/log/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.log

View File

View File

@@ -1,62 +1,194 @@
require "spec_helper"
require 'spec_helper'
class TestJob
def perform;end
def perform(*args)
puts 'Test!'
end
end
class TestFailingJob
def perform(*args)
raise 'Some error'
end
end
class TestNoArgsJob
def perform
puts 'Test!'
end
end
describe Crono::Job do
let(:period) { Crono::Period.new(2.day) }
let(:job) { Crono::Job.new(TestJob, period) }
let(:period) { Crono::Period.new(2.day, at: '15:00') }
let(:job_args) {[{some: 'data'}]}
let(:job) { Crono::Job.new(TestJob, period, []) }
let(:job_with_args) { Crono::Job.new(TestJob, period, job_args) }
let(:failing_job) { Crono::Job.new(TestFailingJob, period, []) }
let(:not_args_job) { Crono::Job.new(TestJob, period) }
it "should contain performer and period" do
it 'should contain performer and period' do
expect(job.performer).to be TestJob
expect(job.period).to be period
end
describe "#perform" do
it "should run performer in separate thread" do
it 'should contain data as JSON String' do
expect(job_with_args.job_args).to eq '[{"some":"data"}]'
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 }
it 'should run performer in separate thread' do
expect(job).to receive(:save)
thread = job.perform.join
expect(thread).to be_stop
end
it "should call Job#save after run" do
expect(job).to receive(:save)
it 'should save performin errors to log' do
thread = failing_job.perform.join
expect(thread).to be_stop
saved_log = Crono::CronoJob.find_by(job_id: failing_job.job_id).log
expect(saved_log).to include 'Some error'
end
it 'should set Job#healthy to false if perform with error' do
failing_job.perform.join
expect(failing_job.healthy).to be false
end
it 'should execute one' do
job.execution_interval = 5.minutes
expect(job).to receive(:perform_job).once
job.perform.join
job.send(:model).destroy
thread = job.perform.join
expect(thread).to be_stop
end
it 'should execute twice' do
job.execution_interval = 0.minutes
test_preform_job_twice
end
it 'should execute twice without args' do
test_preform_job_twice not_args_job
end
it 'should execute twice without initialize execution_interval' do
test_preform_job_twice
end
it 'should call perform of performer' do
expect(TestJob).to receive(:new).with(no_args)
thread = job.perform.join
expect(thread).to be_stop
end
it 'should call perform of performer with data' do
test_job = double()
expect(TestJob).to receive(:new).and_return(test_job)
expect(test_job).to receive(:perform).with([{ 'some' => 'data' }])
thread = job_with_args.perform.join
expect(thread).to be_stop
end
def test_preform_job_twice(jon_instance = job)
expect(jon_instance).to receive(:perform_job).twice
jon_instance.perform.join
thread = jon_instance.perform.join
expect(thread).to be_stop
end
end
describe "#description" do
it "should return job identificator" do
expect(job.description).to be_eql("Perform TestJob every 2 days")
describe '#description' do
it 'should return job identificator' do
expect(job.description).to be_eql('Perform TestJob every 2 days at 15:00')
end
end
describe "#save" do
it "should save new job to DB" do
describe '#save' do
it 'should save new job to DB' do
expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
job.save
expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
end
it "should update saved job" do
job.last_performed_at = Time.now
it 'should update saved job' do
job.last_performed_at = Time.zone.now
job.healthy = true
job.job_args = JSON.generate([{some: 'data'}])
job.save
@crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
expect(@crono_job.last_performed_at).to be_eql(job.last_performed_at)
expect(@crono_job.last_performed_at.utc.to_s).to be_eql job.last_performed_at.utc.to_s
expect(@crono_job.healthy).to be true
end
it 'should save log' do
message = 'test message'
job.send(:log, message)
job.save
expect(job.send(:model).reload.log).to include message
expect(job.job_log.string).to be_empty
end
it 'should not truncate log if not specified' do
log = (1..100).map {|n| "line #{n}" }.join("\n")
job = Crono::Job.new(TestJob, period, [])
job.send(:log, log)
job.save
expect(job.send(:model).reload.log.lines.size).to be >= log.lines.size
end
it 'should truncate log if specified' do
log = (1..100).map {|n| "line #{n}" }.join("\n")
job = Crono::Job.new(TestJob, period, [], truncate_log: 50)
job.send(:log, log)
job.save
expect(job.send(:model).reload.log.lines.size).to be 50
end
end
describe "#load" do
describe '#load' do
before do
@saved_last_performed_at = job.last_performed_at = Time.now
@saved_last_performed_at = job.last_performed_at = Time.zone.now
job.save
end
it "should load info from DB" do
@job = Crono::Job.new(TestJob, period)
it 'should load last_performed_at from DB' do
@job = Crono::Job.new(TestJob, period, job_args)
@job.load
expect(@job.last_performed_at).to be_eql @saved_last_performed_at
expect(@job.last_performed_at.utc.to_s).to be_eql @saved_last_performed_at.utc.to_s
end
end
describe '#log' do
it 'should write log messages to both common and job log' do
message = 'Test message'
job.logfile = "/dev/null"
expect(job.logger).to receive(:log).with(Logger::INFO, message)
expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
job.send(:log, message)
end
it 'should write job log to Job#job_log' do
message = 'Test message'
job.send(:log, message)
expect(job.job_log.string).to include(message)
end
end
describe '#log_error' do
it 'should call log with ERROR severity' do
message = 'Test message'
expect(job).to receive(:log).with(message, Logger::ERROR)
job.send(:log_error, message)
end
end
end

View File

@@ -0,0 +1,31 @@
require 'spec_helper'
require 'rack/test'
module Crono
RSpec.describe Crono::CronoJob, type: :model do
let(:valid_attrs) do
{
job_id: 'Perform TestJob every 3 days'
}
end
it 'should validate presence of job_id' do
@crono_job = Crono::CronoJob.new
expect(@crono_job).not_to be_valid
expect(@crono_job.errors.added?(:job_id, :blank)).to be true
end
it 'should validate uniqueness of job_id' do
Crono::CronoJob.create!(job_id: 'TestJob every 2 days')
@crono_job = Crono::CronoJob.create(job_id: 'TestJob every 2 days')
expect(@crono_job).not_to be_valid
expect(@crono_job.errors.size).to be 1
end
it 'should save job_id to DB' do
Crono::CronoJob.create!(valid_attrs)
@crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
expect(@crono_job).to be_present
end
end
end

View File

@@ -1,28 +0,0 @@
require "spec_helper"
describe Crono::CronoJob do
let(:valid_attrs) do
{
job_id: "Perform TestJob every 3 days"
}
end
it "should validate presence of job_id" do
@crono_job = Crono::CronoJob.new()
expect(@crono_job).not_to be_valid
expect(@crono_job.errors.added?(:job_id, :blank)).to be true
end
it "should validate uniqueness of job_id" do
Crono::CronoJob.create!(job_id: "TestJob every 2 days")
@crono_job = Crono::CronoJob.create(job_id: "TestJob every 2 days")
expect(@crono_job).not_to be_valid
expect(@crono_job.errors.added?(:job_id, :taken)).to be true
end
it "should save job_id to DB" do
Crono::CronoJob.create!(valid_attrs)
@crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
expect(@crono_job).to be_present
end
end

View File

@@ -1,12 +1,45 @@
require "spec_helper"
require 'spec_helper'
class TestJob
def perform;end
def perform
puts 'Test!'
end
end
describe Crono::PerformerProxy do
it "should add job to schedule" do
it 'should add job to schedule' do
expect(Crono.scheduler).to receive(:add_job).with(kind_of(Crono::Job))
Crono.perform(TestJob).every(2.days, at: "15:30")
Crono.perform(TestJob).every(2.days, at: '15:30')
end
it 'should set execution interval' do
allow(Crono).to receive(:scheduler).and_return(Crono::Scheduler.new)
expect_any_instance_of(Crono::Job).to receive(:execution_interval=).with(0.minutes).once
expect_any_instance_of(Crono::Job).to receive(:execution_interval=).with(10.minutes).once
Crono.perform(TestJob).every(2.days, at: '15:30').once_per 10.minutes
end
it 'do nothing when job not initalized' do
expect_any_instance_of(Crono::Job).not_to receive(:execution_interval=)
expect_any_instance_of(described_class).to receive(:once_per)
Crono.perform(TestJob).once_per 10.minutes
end
it 'should add job with args to schedule' do
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), :some, { some: 'data' })
allow(Crono.scheduler).to receive(:add_job)
Crono.perform(TestJob, :some, { some: 'data' }).every(2.days, at: '15:30')
end
it 'should add job without args to schedule' do
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), nil, nil)
allow(Crono.scheduler).to receive(:add_job)
Crono.perform(TestJob).every(2.days, at: '15:30')
end
it 'should add job with options to schedule' do
expect(Crono::Job).to receive(:new).with(TestJob, kind_of(Crono::Period), nil, { some_option: true })
allow(Crono.scheduler).to receive(:add_job)
Crono.perform(TestJob).with_options(some_option: true).every(2.days, at: '15:30')
end
end

View File

@@ -1,46 +1,140 @@
require "spec_helper"
require 'spec_helper'
describe Crono::Period do
around(:each) do |example|
Timecop.freeze do
example.run
describe '#description' do
it 'should return period description' do
@period = Crono::Period.new(1.week, on: :monday, at: '15:20')
expected_description = if ActiveSupport::VERSION::MAJOR >= 5
'every 1 week at 15:20 on Monday'
else
'every 7 days at 15:20 on Monday'
end
expect(@period.description).to be_eql(expected_description)
end
end
describe "#description" do
it "should return period description" do
@period = Crono::Period.new(2.day, at: "15:20")
expect(@period.description).to be_eql("every 2 days at 15:20")
end
end
describe '#next' do
context 'in weakly basis' do
it "should raise error if 'on' is wrong" do
expect { @period = Crono::Period.new(7.days, on: :bad_day) }
.to raise_error("Wrong 'on' day")
end
describe "#next" do
context "in daily basis" do
it "should return the time 2 days from now" do
it 'should raise error when period is less than 1 week' do
expect { @period = Crono::Period.new(6.days, on: :monday) }
.to raise_error("period should be at least 1 week to use 'on'")
end
it "should return a 'on' day" do
@period = Crono::Period.new(1.week, on: :thursday, at: '15:30')
current_week = Time.zone.now.beginning_of_week
last_run_time = current_week.advance(days: 1) # last run on the tuesday
next_run_at = Time.zone.now.next_week.advance(days: 3)
.change(hour: 15, min: 30)
expect(@period.next(since: last_run_time)).to be_eql(next_run_at)
end
it "should return a next week day 'on'" do
@period = Crono::Period.new(1.week, on: :thursday)
Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 4)) do
expect(@period.next).to be_eql(Time.zone.now.next_week.advance(days: 3))
end
end
it 'should return a current week day on the first run if not too late' do
@period = Crono::Period.new(7.days, on: :tuesday)
beginning_of_the_week = Time.zone.now.beginning_of_week
tuesday = beginning_of_the_week.advance(days: 1)
Timecop.freeze(beginning_of_the_week) do
expect(@period.next).to be_eql(tuesday)
end
end
it 'should return today on the first run if not too late' do
@period = Crono::Period.new(1.week, on: :sunday, at: '22:00')
Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 6)
.change(hour: 21, min: 0)) do
expect(@period.next).to be_eql(
Time.zone.now.beginning_of_week.advance(days: 6).change(hour: 22, min: 0)
)
end
end
end
context 'in daily basis' do
it "should return Time.zone.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.zone.now.to_s)
end
it 'should return 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
@period = Crono::Period.new(2.day, at: "15:20")
expect(@period.next).to be_eql(2.day.from_now.change(hour: 15, min: 20))
time = 10.minutes.ago
at = [time.hour, time.min].join(':')
@period = Crono::Period.new(2.days, at: at)
expect(@period.next.to_s).to be_eql(2.days.from_now.change(hour: time.hour, min: time.min).to_s)
end
it "should set time to 'at' time as a hash" do
at = {hour: 18, min: 45}
@period = Crono::Period.new(2.day, at: at)
expect(@period.next).to be_eql(2.day.from_now.change(at))
time = 10.minutes.ago
at = { hour: time.hour, min: time.min }
@period = Crono::Period.new(2.days, at: at)
expect(@period.next.to_s).to be_eql(2.days.from_now.change(at).to_s)
end
it "should raise error when 'at' is wrong" do
expect {
@period = Crono::Period.new(2.day, at: 1)
Crono::Period.new(2.days, at: 1)
}.to raise_error("Unknown 'at' format")
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)
it 'should raise error when period is less than 1 day' do
expect {
Crono::Period.new(5.hours, at: '15:30')
}.to raise_error("period should be at least 1 day to use 'at' with specified hour")
end
it 'should return time in relation to last time' do
@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.days, at: at)
expect(@period.next.utc.to_s).to be_eql(Time.zone.now.change(at).utc.to_s)
end
end
context 'in hourly basis' do
it 'should return next hour minutes if current hour minutes passed' do
Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 20)) do
@period = Crono::Period.new(1.hour, at: { min: 15 })
expect(@period.next.utc.to_s).to be_eql 1.hour.from_now.beginning_of_hour.advance(minutes: 15).utc.to_s
end
end
it 'should return current hour minutes if current hour minutes not passed yet' do
Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 10)) do
@period = Crono::Period.new(1.hour, at: { min: 15 })
expect(@period.next.utc.to_s).to be_eql Time.zone.now.beginning_of_hour.advance(minutes: 15).utc.to_s
end
end
it 'should return next hour minutes within the given interval' do
Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
@period = Crono::Period.new(1.hour, at: { min: 15 }, within: '08:00-16:00')
expect(@period.next.utc.to_s).to be_eql Time.zone.now.tomorrow.change(hour: 8, min: 15).utc.to_s
end
Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
@period = Crono::Period.new(1.hour, at: { min: 15 }, within: '23:00-07:00')
expect(@period.next.utc.to_s).to be_eql Time.zone.now.change(hour: 23, min: 15).utc.to_s
end
end
end
end

89
spec/rails_helper.rb Normal file
View File

@@ -0,0 +1,89 @@
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../spec/internal/config/environment.rb', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'bundler/setup'
Bundler.setup
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
require 'timecop'
require 'byebug'
require 'crono'
# setting default time zone
# In Rails project, Time.zone_default equals "UTC"
Time.zone_default = Time.find_zone('UTC')
ActiveRecord::Base.logger = Logger.new($stdout)
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: ':memory:'
)
ActiveRecord::Schema.define do
require_relative '../lib/generators/crono/install/templates/migrations/create_crono_jobs.rb'
@migration_version = '[6.1]'
end
CreateCronoJobs.up
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
# config.use_transactional_fixtures = true
# You can uncomment this line to turn off ActiveRecord support entirely.
# config.use_active_record = false
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, type: :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
end

View File

@@ -1,35 +1,62 @@
require "spec_helper"
require 'spec_helper'
class TestJob
def perform;end
def perform
puts 'Test!'
end
end
describe Crono::Scheduler do
before(:each) do
@scheduler = Crono::Scheduler.new
@jobs = [
Crono::Period.new(3.day, at: "18:55"),
Crono::Period.new(1.day, at: "15:30"),
Crono::Period.new(7.day, at: "06:05")
].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"))
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
it "should return next job in schedule" do
expect(@scheduler.next).to be @jobs[1]
describe '#next_jobs' do
it 'should return next job in schedule' do
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 next based on last" do
expect(@scheduler.next)
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,13 +1,19 @@
require 'bundler/setup'
Bundler.setup
require 'bundler'
require 'timecop'
require 'crono'
require 'generators/crono/install/templates/migrations/create_crono_jobs.rb'
Bundler.require :default, :development
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)
CreateCronoJobs.up
# If you're using all parts of Rails:
Combustion.initialize! :all
# Or, load just what you need:
# Combustion.initialize! :active_record, :action_controller
require 'rspec/rails'
# If you're using Capybara:
# require 'capybara/rails'
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.mock_with :rspec do |mocks|
mocks.allow_message_expectations_on_nil = true
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

61
spec/web_spec.rb Normal file
View File

@@ -0,0 +1,61 @@
require 'spec_helper'
require 'rack/test'
include Rack::Test::Methods
describe Crono::Engine do
let(:app) { Crono::Engine }
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!(
job_id: @test_job_id,
log: @test_job_log
)
end
after { @test_job.destroy }
describe '/' do
it 'should show all jobs' do
get '/'
expect(last_response).to be_ok
expect(last_response.body).to include @test_job_id
end
it 'should show a error mark when a job is unhealthy' do
@test_job.update(healthy: false, last_performed_at: 10.minutes.ago)
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, last_performed_at: 10.minutes.ago)
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
it 'should show job log' do
get "/jobs/#{@test_job.id}"
expect(last_response).to be_ok
expect(last_response.body).to include @test_job_id
expect(last_response.body).to include @test_job_log
end
it 'should show a message about the unhealthy job' do
message = 'An error occurs during the last execution of this job'
@test_job.update(healthy: false)
get "/jobs/#{@test_job.id}"
expect(last_response.body).to include message
end
end
end