commit d2eec9f94a43f53eb46fe1bec0ae681b5d874806 Author: Javan Makhmali Date: Sun Feb 15 19:24:10 2009 -0800 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..835b14c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +pkg +doc +Manifest \ No newline at end of file diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc new file mode 100644 index 0000000..62c88d7 --- /dev/null +++ b/CHANGELOG.rdoc @@ -0,0 +1,3 @@ +== 0.1.0 / February 15th, 2008 + +* Initial release \ No newline at end of file diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..c8493f7 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,79 @@ += Whenever + +Whenever is a ruby gem that provides a ruby syntax for defining cron jobs. It was designed to work well with Rails applications, but can be used independently as well. + +== Installation + +To install Whenever in a Rails (2.1 or greater) application: + +in your "config/environment.rb" file: + + Rails::Initializer.run do |config| + config.gem 'javan-whenever', :lib => 'whenever', :source => 'http://gems.github.com' + end + +To install this gem (and all other missing gem dependencies), run rake gems:install (use sudo if necessary). + +In older versions of Rails: + +$ gem sources -a http://gems.github.com +$ gem install whenever + +in your "config/environment.rb" file: + + Rails::Initializer.run do |config| + ... + end + + require "whenever" + +== Getting started + +$ cd /my/rails/app +$ wheneverize . + +This will create an initial "config/schedule.rb" file you. + +== Example schedule.rb file + + set :runner_path, '/var/www/apps/my_app' # Whenever will try to use your RAILS_ROOT if this isn't set + set :runner_environment, :production # Whenever defaults to production so only set this if you want to use a different environment. + set :cron_log, '/path/to/my/cronlog.log' # Where to log (this should NOT be your Rails log) + + + every 2.hours do + runner "MyModel.some_process" # runners are the script/runners you know and love + command "/usr/local/bin/my_great_command" # commands are any unix command + end + + every 1.day, :at => '4:30 am' do # If not :at option is set these jobs will run at midnight + runner "DB.Backup", :cron_log => false # You can specify false for no logging or a string a different log file to override logging. + end + + every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot + runner "SomeModel.ladeda" + end + + every :sunday do # Use any day of the week or :weekend, :weekday + runner "Task.do_something_great" + end + +== Cron output + +$ cd /my/rails/app +$ whenever + +And you'll see your schedule.rb converted to cron sytax + +== Capistrano integration + +Use the "whenever:write_cron" task to automatically write your crontab file with each deploy. + +in your "config/deploy.rb" file do something like: + + after "deploy:symlink", "whenever:write_cron" + +THIS WILL COMPLETELY OVERWRITE ANY EXISTING CRONTAB ENTRIES! +------------------------------------------------------------ + +Better documentation on the way! diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..50ca4c2 --- /dev/null +++ b/Rakefile @@ -0,0 +1,14 @@ +require 'rubygems' +require 'rake' +require 'echoe' +require 'lib/base' + +Echoe.new('whenever', Whenever::VERSION) do |p| + p.description = "Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever." + p.url = "http://github.com/javan/whenever" + p.author = "Javan Makhmali" + p.email = "javan@javan.us" + p.dependencies = ["chronic", "activesupport"] + p.ignore_pattern = ["tmp/*", "script/*"] + p.development_dependencies = [] +end \ No newline at end of file diff --git a/bin/whenever b/bin/whenever new file mode 100644 index 0000000..4e8b733 --- /dev/null +++ b/bin/whenever @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require 'rake' +require 'optparse' +require 'whenever' + +task = "whenever:output_cron" + +OptionParser.new do |opts| + opts.banner = "Usage: whenever [options]" + opts.on('-c', '--write-crontab') { task = "whenever:write_cron" } + opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION}"; exit } +end.parse! + +Rake::Task[task].invoke \ No newline at end of file diff --git a/bin/wheneverize b/bin/wheneverize new file mode 100644 index 0000000..50dbda2 --- /dev/null +++ b/bin/wheneverize @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +# This file is based heavily on Capistrano's `capify` command + +require 'optparse' + +OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename($0)} [path]" + + begin + opts.parse!(ARGV) + rescue OptionParser::ParseError => e + warn e.message + puts opts + exit 1 + end +end + +if ARGV.empty? + abort "Please specify the directory to wheneverize, e.g. `#{File.basename($0)} .'" +elsif !File.exists?(ARGV.first) + abort "`#{ARGV.first}' does not exist." +elsif !File.directory?(ARGV.first) + abort "`#{ARGV.first}' is not a directory." +elsif ARGV.length > 1 + abort "Too many arguments; please specify only the directory to wheneverize." +end + + +content = <<-FILE +# Use this file to easily define all of your cron jobs. +# +# It's helpful, but not entirely necessary to understand cron before proceeding. +# http://en.wikipedia.org/wiki/Cron + +# Example: +# +# set :cron_log, "/path/to/my/cron_log.log" +# +# every 2.hours do +# command "/usr/bin/some_great_command" +# runner "MyModel.some_method" +# end +# +# every 4.days do +# runner "AnotherModel.prune_old_records" +# end + +# Learn more: http://github.com/javan/whenever +FILE + +file = 'config/schedule.rb' +base = ARGV.shift + +file = File.join(base, file) +if File.exists?(file) + warn "[skip] `#{file}' already exists" +elsif File.exists?(file.downcase) + warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'" +elsif !File.exists?(File.dirname(file)) + warn "[skip] directory `#{File.dirname(file)}' does not exist" +else + puts "[add] writing `#{file}'" + File.open(file, "w") { |f| f.write(content) } +end + +puts "[done] wheneverized!" diff --git a/lib/base.rb b/lib/base.rb new file mode 100644 index 0000000..de58448 --- /dev/null +++ b/lib/base.rb @@ -0,0 +1,12 @@ +require 'job_list' +require 'job_types/default' +require 'job_types/runner' +require 'outputs/cron' + +module Whenever + VERSION = '0.1.0' + + def self.cron(options) + Whenever::JobList.new(options).generate_cron_output + end +end \ No newline at end of file diff --git a/lib/job_list.rb b/lib/job_list.rb new file mode 100644 index 0000000..d634de1 --- /dev/null +++ b/lib/job_list.rb @@ -0,0 +1,84 @@ +module Whenever + class JobList + + def initialize(options) + @jobs = Hash.new + @env = Hash.new + + config = case options + when String then options + when Hash + if options[:string] + options[:string] + elsif options[:file] + File.read(options[:file]) + end + end + + eval(config) + end + + def set(variable, value) + instance_variable_set("@#{variable}".to_sym, value) + self.class.send(:attr_reader, variable.to_sym) + end + + def env(variable, value) + @env[variable.to_s] = value + end + + def every(frequency, options = {}) + @current_time_scope = frequency + @options = options + yield + end + + def command(task, options = {}) + options[:cron_log] ||= @cron_log unless options[:cron_log] === false + options[:class] ||= Whenever::Job::Default + @jobs[@current_time_scope] ||= [] + @jobs[@current_time_scope] << options[:class].new(@options.merge(:task => task).merge(options)) + end + + def runner(task, options = {}) + options.reverse_merge!(:environment => @runner_environment, :path => @runner_path) + options[:class] = Whenever::Job::Runner + command(task, options) + end + + def generate_cron_output + [environment_variables, cron_jobs].compact.join + end + + private + + def environment_variables + return if @env.empty? + + output = [] + @env.each do |key, val| + output << "#{key}=#{val}\n" + end + output << "\n" + + output.join + end + + def cron_jobs + return if @jobs.empty? + + output = [] + @jobs.each do |time, jobs| + jobs.each do |job| + cron = Whenever::Output::Cron.output(time, job) + cron << " >> #{job.cron_log} 2>&1" if job.cron_log + cron << "\n\n" + output << cron + end + end + + output.join + end + + end +end \ No newline at end of file diff --git a/lib/job_types/default.rb b/lib/job_types/default.rb new file mode 100644 index 0000000..b787d93 --- /dev/null +++ b/lib/job_types/default.rb @@ -0,0 +1,17 @@ +module Whenever + module Job + class Default + attr_accessor :task, :at, :cron_log + + def initialize(options = {}) + @task = options[:task] + @at = options[:at] + @cron_log = options[:cron_log] + end + + def output + task + end + end + end +end \ No newline at end of file diff --git a/lib/job_types/runner.rb b/lib/job_types/runner.rb new file mode 100644 index 0000000..8d6049d --- /dev/null +++ b/lib/job_types/runner.rb @@ -0,0 +1,30 @@ +module Whenever + module Job + class Runner < Whenever::Job::Default + + def initialize(options = {}) + super(options) + + @environment = options[:environment] || :production + + if [Whenever::Job::Runner.rails_root, options[:path]].all?(&:blank?) + raise ArgumentError, "no cron_path available for runner to use" + else + @path = options[:path] || Whenever::Job::Runner.rails_root + end + end + + def self.rails_root + if defined?(RAILS_ROOT) + RAILS_ROOT + elsif defined?(::RAILS_ROOT) + ::RAILS_ROOT + end + end + + def output + %Q(#{File.join(@path, 'script', 'runner')} -e #{@environment} "#{task}") + end + end + end +end diff --git a/lib/outputs/cron.rb b/lib/outputs/cron.rb new file mode 100644 index 0000000..f1ad2af --- /dev/null +++ b/lib/outputs/cron.rb @@ -0,0 +1,104 @@ +module Whenever + module Output + + class Cron + + attr_accessor :time, :task + + def initialize(time = nil, task = nil, at = nil) + @time = time + @task = task + @at = at.is_a?(String) ? (Chronic.parse(at) || 0) : (at || 0) + end + + def self.output(time, job) + out = new(time, job.output, job.at) + "#{out.time_in_cron_syntax} #{out.task}" + end + + def time_in_cron_syntax + case @time + when Symbol then parse_symbol + when String then parse_as_string + else parse_time + end + end + + protected + + def parse_symbol + case @time + when :reboot then '@reboot' + when :year, :yearly then '@annually' + when :day, :daily then '@daily' + when :midnight then '@midnight' + when :month, :monthly then '@monthly' + when :week, :weekly then '@weekly' + when :hour, :hourly then '@hourly' + else parse_as_string + end + end + + def parse_time + timing = Array.new(5, '*') + case @time + when 0.seconds...1.minute + raise ArgumentError, "Time must be in minutes or higher" + when 1.minute...1.hour + minute_frequency = @time / 60 + timing[0] = comma_separated_timing(minute_frequency, 59) + when 1.hour...1.day + hour_frequency = (@time / 60 / 60).round + timing[0] = @at.is_a?(Time) ? @at.min : @at + timing[1] = comma_separated_timing(hour_frequency, 23) + when 1.day...1.month + day_frequency = (@time / 24 / 60 / 60).round + timing[0] = @at.is_a?(Time) ? @at.min : 0 + timing[1] = @at.is_a?(Time) ? @at.hour : @at + timing[2] = comma_separated_timing(day_frequency, 31, 1) + when 1.month..12.months + month_frequency = (@time / 30 / 24 / 60 / 60).round + timing[0] = @at.is_a?(Time) ? @at.min : 0 + timing[1] = @at.is_a?(Time) ? @at.hour : 0 + timing[2] = @at.is_a?(Time) ? @at.day : (@at.zero? ? 1 : @at) + timing[3] = comma_separated_timing(month_frequency, 12, 1) + else + return parse_as_string + end + timing.join(' ') + end + + def parse_as_string + return unless @time + string = @time.to_s + + return "0 0 * * mon-fri" if string.downcase.index('weekday') + return "0 0 * * sat,sun" if string.downcase.index('weekend') + + %w(sun mon tue wed thu fri sat).each do |day| + return "0 0 * * #{day}" if string.downcase.index(day) + end + + raise ArgumentError, "Couldn't parse: #{@time}" + end + + def comma_separated_timing(frequency, max, start = 0) + return start if frequency.blank? || frequency.zero? + return '*' if frequency == 1 + return frequency if frequency > (max * 0.5).ceil + + original_start = start + + start += frequency unless (max + 1).modulo(frequency).zero? || start > 0 + output = (start..max).step(frequency).to_a + + max_occurances = (max.to_f / (frequency.to_f)).round + max_occurances += 1 if original_start.zero? + + output[0, max_occurances].join(',') + end + + end + + end +end \ No newline at end of file diff --git a/lib/tasks/whenever.rake b/lib/tasks/whenever.rake new file mode 100644 index 0000000..67c9266 --- /dev/null +++ b/lib/tasks/whenever.rake @@ -0,0 +1,21 @@ +namespace :whenever do + + desc "outputs cron" + task :output_cron do + puts Whenever.cron(:file => "config/schedule.rb") + end + + desc "writes cron" + task :write_cron do + require 'tempfile' + cron_output = Whenever.cron(:file => "config/schedule.rb") + + tmp_cron_file = Tempfile.new('whenever_tmp_cron').path + File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file| + file << cron_output + end + sh "crontab #{tmp_cron_file}" + puts "[write] crontab file updated" + end + +end \ No newline at end of file diff --git a/lib/whenever.rb b/lib/whenever.rb new file mode 100644 index 0000000..fd24b29 --- /dev/null +++ b/lib/whenever.rb @@ -0,0 +1,24 @@ +unless defined?(Whenever) + $:.unshift(File.dirname(__FILE__)) + + # Hoping to load Rails' Rakefile + begin + load 'Rakefile' + rescue LoadError => e + nil + end + + # Load Whenever's rake tasks + begin + Dir[File.join(File.dirname(__FILE__), 'tasks', '*.rake')].each { |rake| load rake } + rescue LoadError => e + nil + end + +end + + +require 'activesupport' +require 'chronic' + +require 'base' diff --git a/test/cron_test.rb b/test/cron_test.rb new file mode 100644 index 0000000..e8e3a33 --- /dev/null +++ b/test/cron_test.rb @@ -0,0 +1,187 @@ +require 'test_helper' + +class CronTest < Test::Unit::TestCase + + context "When parsing time in minutes" do + should "raise if less than 1 minute" do + assert_raises ArgumentError do + parse_time(59.seconds) + end + + assert_raises ArgumentError do + parse_time(0.minutes) + end + end + + # For santity, do some tests on straight String + should "parse correctly" do + assert_equal '* * * * *', parse_time(1.minute) + assert_equal '0,5,10,15,20,25,30,35,40,45,50,55 * * * *', parse_time(5.minutes) + assert_equal '7,14,21,28,35,42,49,56 * * * *', parse_time(7.minutes) + assert_equal '0,30 * * * *', parse_time(30.minutes) + assert_equal '32 * * * *', parse_time(32.minutes) + assert_not_equal '60 * * * *', parse_time(60.minutes) # 60 minutes bumps up into the hour range + end + + # Test all minutes + (2..59).each do |num| + should "parse correctly for #{num} minutes" do + start = 0 + start += num unless 60.modulo(num).zero? + minutes = (start..59).step(num).to_a + + assert_equal "#{minutes.join(',')} * * * *", parse_time(num.minutes) + end + end + end + + context "When parsing time in hours" do + should "parse correctly" do + assert_equal '0 * * * *', parse_time(1.hour) + assert_equal '0 0,2,4,6,8,10,12,14,16,18,20,22 * * *', parse_time(2.hours) + assert_equal '0 0,3,6,9,12,15,18,21 * * *', parse_time(3.hours) + assert_equal '0 5,10,15,20 * * *', parse_time(5.hours) + assert_equal '0 17 * * *', parse_time(17.hours) + assert_not_equal '0 24 * * *', parse_time(24.hours) # 24 hours bumps up into the day range + end + + (2..23).each do |num| + should "parse correctly for #{num} hours" do + start = 0 + start += num unless 24.modulo(num).zero? + hours = (start..23).step(num).to_a + + assert_equal "0 #{hours.join(',')} * * *", parse_time(num.hours) + end + end + + should "parse correctly when given an 'at' with minutes as an Integer" do + assert_minutes_equals "1", 1 + assert_minutes_equals "14", 14 + assert_minutes_equals "27", 27 + assert_minutes_equals "55", 55 + end + + should "parse correctly when given an 'at' with minutes as a Time" do + # Basically just testing that Chronic parses some times and we get the minutes out of it + assert_minutes_equals "1", '3:01am' + assert_minutes_equals "1", 'January 21 2:01 PM' + assert_minutes_equals "0", 'midnight' + assert_minutes_equals "59", '13:59' + end + end + + context "When parsing time in days (of month)" do + should "parse correctly" do + assert_equal '0 0 * * *', parse_time(1.days) + assert_equal '0 0 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31 * *', parse_time(2.days) + assert_equal '0 0 1,5,9,13,17,21,25,29 * *', parse_time(4.days) + assert_equal '0 0 1,8,15,22 * *', parse_time(7.days) + assert_equal '0 0 1,17 * *', parse_time(16.days) + assert_equal '0 0 17 * *', parse_time(17.days) + assert_equal '0 0 29 * *', parse_time(29.days) + assert_not_equal '0 0 30 * *', parse_time(30.days) # 30 days bumps into the month range + end + + should "parse correctly when given an 'at' with hours, minutes as a Time" do + # first param is an array with [hours, minutes] + assert_hours_and_minutes_equals %w(3 45), '3:45am' + assert_hours_and_minutes_equals %w(20 1), '8:01pm' + assert_hours_and_minutes_equals %w(0 0), 'midnight' + assert_hours_and_minutes_equals %w(1 23), '1:23 AM' + assert_hours_and_minutes_equals %w(23 59), 'March 21 11:59 pM' + end + + should "parse correctly when given an 'at' with hours as an Integer" do + # first param is an array with [hours, minutes] + assert_hours_and_minutes_equals %w(1 0), 1 + assert_hours_and_minutes_equals %w(3 0), 3 + assert_hours_and_minutes_equals %w(15 0), 15 + assert_hours_and_minutes_equals %w(19 0), 19 + assert_hours_and_minutes_equals %w(23 0), 23 + end + end + + context "When parsing time in months" do + should "parse correctly" do + assert_equal '0 0 1 * *', parse_time(1.month) + assert_equal '0 0 1 1,3,5,7,9,11 *', parse_time(2.months) + assert_equal '0 0 1 1,4,7,10 *', parse_time(3.months) + assert_equal '0 0 1 1,5,9 *', parse_time(4.months) + assert_equal '0 0 1 1,6 *', parse_time(5.months) + assert_equal '0 0 1 7 *', parse_time(7.months) + assert_equal '0 0 1 8 *', parse_time(8.months) + assert_equal '0 0 1 9 *', parse_time(9.months) + assert_equal '0 0 1 10 *', parse_time(10.months) + assert_equal '0 0 1 11 *', parse_time(11.months) + assert_equal '0 0 1 12 *', parse_time(12.months) + end + + should "parse correctly when given an 'at' with days, hours, minutes as a Time" do + # first param is an array with [days, hours, minutes] + assert_days_and_hours_and_minutes_equals %w(1 3 45), 'January 1st 3:45am' + assert_days_and_hours_and_minutes_equals %w(11 23 0), 'Feb 11 11PM' + assert_days_and_hours_and_minutes_equals %w(22 1 1), 'march 22nd at 1:01 am' + assert_days_and_hours_and_minutes_equals %w(23 0 0), 'march 22nd at midnight' # looks like midnight means the next day + end + + should "parse correctly when given an 'at' with days as an Integer" do + # first param is an array with [days, hours, minutes] + assert_days_and_hours_and_minutes_equals %w(1 0 0), 1 + assert_days_and_hours_and_minutes_equals %w(15 0 0), 15 + assert_days_and_hours_and_minutes_equals %w(29 0 0), 29 + end + end + + context "When parsing time in days (of week)" do + should "parse days of the week correctly" do + { + 'sun' => %w(sun Sunday SUNDAY SUN), + 'mon' => %w(mon Monday MONDAY MON), + 'tue' => %w(tue tues Tuesday TUESDAY TUE), + 'wed' => %w(wed Wednesday WEDNESDAY WED), + 'thu' => %w(thu thurs thur Thursday THURSDAY THU), + 'fri' => %w(fri Friday FRIDAY FRI), + 'sat' => %w(sat Saturday SATURDAY SAT) + }.each do |day, day_tests| + day_tests.each do |day_test| + assert_equal "0 0 * * #{day}", parse_time(day_test) + end + end + end + + should "parse weekday correctly" do + assert_equal '0 0 * * mon-fri', parse_time('weekday') + assert_equal '0 0 * * mon-fri', parse_time('Weekdays') + end + + should "parse weekend correctly" do + assert_equal '0 0 * * sat,sun', parse_time('weekend') + assert_equal '0 0 * * sat,sun', parse_time('Weekends') + end + end + +private + + def assert_days_and_hours_and_minutes_equals(expected, time) + cron = parse_time(2.months, 'some task', time) + minutes, hours, days, *garbage = cron.split(' ') + assert_equal expected, [days, hours, minutes] + end + + def assert_hours_and_minutes_equals(expected, time) + cron = parse_time(2.days, 'some task', time) + minutes, hours, *garbage = cron.split(' ') + assert_equal expected, [hours, minutes] + end + + def assert_minutes_equals(expected, time) + cron = parse_time(2.hours, 'some task', time) + assert_equal expected, cron.split(' ')[0] + end + + def parse_time(time = nil, task = nil, at = nil) + Whenever::Output::Cron.new(time, task, at).time_in_cron_syntax + end + +end \ No newline at end of file diff --git a/test/output_command_test.rb b/test/output_command_test.rb new file mode 100644 index 0000000..110dd3f --- /dev/null +++ b/test/output_command_test.rb @@ -0,0 +1,70 @@ +require 'test_helper' + +class OutputCommandTest < Test::Unit::TestCase + + context "A plain command" do + setup do + @output = load_whenever_output \ + <<-file + every 2.hours do + command "blahblah" + end + file + end + + should "output the command" do + assert_match /^.+ .+ .+ .+ blahblah$/, @output + end + end + + context "A command when the cron_log is set" do + setup do + @output = load_whenever_output \ + <<-file + set :cron_log, 'logfile.log' + every 2.hours do + command "blahblah" + end + file + end + + should "output the command with the log syntax appended" do + assert_match /^.+ .+ .+ .+ blahblah >> logfile.log 2>&1$/, @output + end + end + + context "A command when the cron_log is set and the comand overrides it" do + setup do + @output = load_whenever_output \ + <<-file + set :cron_log, 'logfile.log' + every 2.hours do + command "blahblah", :cron_log => 'otherlog.log' + end + file + end + + should "output the command with the log syntax appended" do + assert_no_match /.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, @output + assert_match /^.+ .+ .+ .+ blahblah >> otherlog.log 2>&1$/, @output + end + end + + context "A command when the cron_log is set and the comand rejects it" do + setup do + @output = load_whenever_output \ + <<-file + set :cron_log, 'logfile.log' + every 2.hours do + command "blahblah", :cron_log => false + end + file + end + + should "output the command without the log syntax appended" do + assert_no_match /.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, @output + assert_match /^.+ .+ .+ .+ blahblah$/, @output + end + end + +end \ No newline at end of file diff --git a/test/output_env_test.rb b/test/output_env_test.rb new file mode 100644 index 0000000..9d2ec5d --- /dev/null +++ b/test/output_env_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +class OutputEnvTest < Test::Unit::TestCase + + context "The output from Whenever with environment variables set" do + setup do + @output = load_whenever_output \ + <<-file + env :MYVAR, 'blah' + env 'MAILTO', "someone@example.com" + file + end + + should "output MYVAR environment variable" do + assert_match "MYVAR=blah", @output + end + + should "output MAILTO environment variable" do + assert_match "MAILTO=someone@example.com", @output + end + end + +end \ No newline at end of file diff --git a/test/output_runner_test.rb b/test/output_runner_test.rb new file mode 100644 index 0000000..5622362 --- /dev/null +++ b/test/output_runner_test.rb @@ -0,0 +1,75 @@ +require 'test_helper' + +class OutputRunnerTest < Test::Unit::TestCase + + context "A runner with runner_path set" do + setup do + @output = load_whenever_output \ + <<-file + set :runner_path, '/my/path' + every 2.hours do + runner "blahblah" + end + file + end + + should "output the runner using that path" do + assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output + end + end + + context "A runner with no runner_path set and RAILS_ROOT defined" do + setup do + Whenever::Job::Runner.stubs(:rails_root).returns('/my/path') + + @output = load_whenever_output \ + <<-file + every 2.hours do + runner "blahblah" + end + file + end + + should "output the runner using that path" do + assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output + end + end + + context "A runner with runner_path set AND RAILS_ROOT defined" do + setup do + Whenever::Job::Runner.stubs(:rails_root).returns('/my/path') + + @output = load_whenever_output \ + <<-file + set :runner_path, '/my/path' + every 2.hours do + runner "blahblah" + end + file + end + + should "use the runner_path" do + assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output + assert_no_match /\/rails\/path/, @output + end + end + + context "A runner with no runner_path set and no RAILS_ROOT defined" do + setup do + Whenever::Job::Runner.stubs(:rails_root).returns(nil) + + @input = <<-file + every 2.hours do + runner "blahblah" + end + file + end + + should "raise an exception" do + assert_raises ArgumentError do + load_whenever_output(@input) + end + end + end + +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..ead54f2 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,35 @@ +require 'rubygems' +require 'test/unit' + +begin + require 'shoulda' +rescue LoadError + warn 'To test Whenever you need the shoulda gem:' + warn '$ sudo gem install thoughtbot-shoulda' + exit(1) +end + +begin + require 'mocha' +rescue LoadError + warn 'To test Whenever you need the mocha gem:' + warn '$ sudo gem install mocha' + exit(1) +end + +require 'whenever' + +module TestExtensions + + def load_whenever_output(input) + Whenever.cron(input) + end + + def two_hours + "0 0,2,4,6,8,10,12,14,16,18,20,22 * * *" + end +end + +class Test::Unit::TestCase + include TestExtensions +end