schedule 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +1 -0
- data/Rakefile +115 -0
- data/bin/cr +49 -0
- data/lib/schedule.rb +8 -0
- data/lib/schedule/logger.rb +56 -0
- data/lib/schedule/notifier.rb +3 -0
- data/lib/schedule/notifier/base.rb +21 -0
- data/lib/schedule/notifier/console.rb +24 -0
- data/lib/schedule/notifier/email.rb +42 -0
- data/lib/schedule/runner.rb +101 -0
- data/lib/schedule/task.rb +10 -0
- data/test/commands/flushing +7 -0
- data/test/commands/raises_exception +6 -0
- data/test/commands/success +5 -0
- data/test/integration/binary_test.rb +44 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/logger_test.rb +109 -0
- data/test/unit/runner_test.rb +89 -0
- data/test/unit/task_test.rb +18 -0
- metadata +109 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
A Ruby replacement for Cron
|
data/Rakefile
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake/gempackagetask"
|
3
|
+
require "rake/rdoctask"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
spec = Gem::Specification.new do |s|
|
9
|
+
s.name = "schedule"
|
10
|
+
s.version = "0.0.1"
|
11
|
+
s.summary = "A Ruby replacement for Cron"
|
12
|
+
s.author = "Mark Dodwell"
|
13
|
+
s.email = "hi@mkdynamic.co.uk"
|
14
|
+
s.homepage = "http://github.com/mkdynamic/schedule"
|
15
|
+
|
16
|
+
# Rdoc
|
17
|
+
# s.has_rdoc = true
|
18
|
+
# s.extra_rdoc_files = %w(README.md)
|
19
|
+
# s.rdoc_options = %w(--main README.md)
|
20
|
+
|
21
|
+
# Files
|
22
|
+
s.files = %w(Rakefile README.md) + Dir.glob("{bin,lib,test}/**/*")
|
23
|
+
s.executables = FileList["bin/**"].map { |f| File.basename(f) }
|
24
|
+
s.require_paths = ["bin", "lib"]
|
25
|
+
|
26
|
+
# Dependencies
|
27
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.3")
|
28
|
+
s.add_development_dependency("shoulda")
|
29
|
+
s.add_development_dependency("timecop")
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Specification
|
34
|
+
#
|
35
|
+
|
36
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
37
|
+
task :gemspec do
|
38
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
39
|
+
File.open(file, "w") { |f| f.write(spec.to_ruby) }
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Build
|
44
|
+
#
|
45
|
+
|
46
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
47
|
+
pkg.gem_spec = spec
|
48
|
+
end
|
49
|
+
|
50
|
+
task :package => :gemspec # build gemspec when packaging
|
51
|
+
|
52
|
+
#
|
53
|
+
# Tests
|
54
|
+
#
|
55
|
+
|
56
|
+
Rake::TestTask.new do |t|
|
57
|
+
t.libs = [File.expand_path("lib"), "test"]
|
58
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
59
|
+
t.verbose = true
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Documentation
|
64
|
+
#
|
65
|
+
|
66
|
+
# Rake::RDocTask.new do |rd|
|
67
|
+
# rd.main = "README.md"
|
68
|
+
# rd.rdoc_files.include("README.md", "lib/**/*.rb")
|
69
|
+
# rd.rdoc_dir = "rdoc"
|
70
|
+
# end
|
71
|
+
|
72
|
+
# desc "Clear out RDoc and generated packages"
|
73
|
+
# task :clean => [:clobber_rdoc, :clobber_package] do
|
74
|
+
# rm "#{spec.name}.gemspec"
|
75
|
+
# end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Release
|
79
|
+
#
|
80
|
+
|
81
|
+
desc "Tag the repository in git with gem version number"
|
82
|
+
task :tag do
|
83
|
+
#`git fetch --tags`
|
84
|
+
if `git tag`.split("\n").include?("v#{spec.version}")
|
85
|
+
raise "Version #{spec.version} has already been released."
|
86
|
+
end
|
87
|
+
|
88
|
+
changed_files = `git diff --cached --name-only`.split("\n") + `git diff --name-only`.split("\n")
|
89
|
+
if changed_files == ["Rakefile"]
|
90
|
+
# MAGIC! automatically update Schedule::VERSION constant
|
91
|
+
file = "lib/#{spec.name}.rb"
|
92
|
+
s = File.read(file)
|
93
|
+
File.open(file, "w") do |f|
|
94
|
+
f.write(s.gsub(/^(\s*)VERSION(\s*)=(\s*)(["'])[\d\.]+(['"])(\s*)$/, %{\\1VERSION\\2=\\3\\4#{spec.version.to_s}\\5\\6}))
|
95
|
+
end
|
96
|
+
if File.read(file).match(/^\s*VERSION\s*=\s*"([\d\.]+)"\s*$/)[1] != spec.version.to_s
|
97
|
+
"VERSION constant in #{file} differs from gemspec version."
|
98
|
+
end
|
99
|
+
|
100
|
+
Rake::Task["package"].invoke
|
101
|
+
|
102
|
+
`git add #{File.expand_path("../#{spec.name}.gemspec", __FILE__)} Rakefile lib/schedule.rb`
|
103
|
+
`git commit -m "prepare version #{spec.version}"`
|
104
|
+
`git tag v#{spec.version}`
|
105
|
+
#`git push --tags`
|
106
|
+
#`git push`
|
107
|
+
else
|
108
|
+
raise "Repository contains uncommitted changes; either commit or stash."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# desc "Tag and publish the gem to rubygems.org"
|
113
|
+
# task :publish => :tag do
|
114
|
+
# `gem push pkg/#{spec.name}-#{spec.version}.gem`
|
115
|
+
# end
|
data/bin/cr
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require "schedule/task"
|
7
|
+
require "schedule/runner"
|
8
|
+
require "schedule/notifier"
|
9
|
+
require "schedule/logger"
|
10
|
+
|
11
|
+
def invalid_arg!(arg, options, command)
|
12
|
+
raise "Invalid argument: #{options.inspect} / #{command.inspect} / #{arg.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
args = ARGV.dup
|
16
|
+
command = nil
|
17
|
+
options = {}
|
18
|
+
until args.empty? do
|
19
|
+
case (arg = args.shift.strip)
|
20
|
+
when /^[^\-]/
|
21
|
+
invalid_arg!(arg, options, command) unless command.nil?
|
22
|
+
command = arg
|
23
|
+
when "-n", "--name" then options[:name] = args.shift
|
24
|
+
when "-l", "--log" then options[:log] = args.shift
|
25
|
+
when "-e", "--email" then options[:email] = args.shift
|
26
|
+
when "--" # noop
|
27
|
+
else invalid_arg!(arg, options, command)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
raise "Did not pass any command to run" unless command
|
32
|
+
|
33
|
+
task = Schedule::Task.new(options[:name], command)
|
34
|
+
|
35
|
+
logger = if options[:log]
|
36
|
+
Schedule::Logger.new(options[:log], options[:name])
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
notifier = if options[:email]
|
42
|
+
Schedule::Notifier::Email.new({
|
43
|
+
:to => options[:email]
|
44
|
+
})
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
exit Schedule::Runner.new(task, logger, notifier).run
|
data/lib/schedule.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module Schedule
|
5
|
+
class Logger
|
6
|
+
LINE_SEPERATOR = /\r\n|\n|\r/ # CRLF, LF or CR
|
7
|
+
|
8
|
+
attr_reader :buffer
|
9
|
+
|
10
|
+
def initialize(device, prefix)
|
11
|
+
if device == STDOUT
|
12
|
+
@device = device
|
13
|
+
elsif device.is_a?(String)
|
14
|
+
@device = File.open(device, File::WRONLY | File::APPEND | File::CREAT)
|
15
|
+
@device.sync = true
|
16
|
+
else
|
17
|
+
raise ArgumentError, "Log device must be a file path or STDOUT"
|
18
|
+
end
|
19
|
+
|
20
|
+
@formatter = Proc.new { |line, timestamp| "#{timestamp.strftime("%Y-%m-%d %H:%M:%S")} [#{@prefix}] #{line}" }
|
21
|
+
@buffer = StringIO.new
|
22
|
+
@prefix = prefix
|
23
|
+
@semaphore = Mutex.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def log(msg)
|
27
|
+
timestamp = Time.now
|
28
|
+
|
29
|
+
# split into lines, preserving whitespace
|
30
|
+
lines = msg.split(LINE_SEPERATOR)
|
31
|
+
msg.scan(LINE_SEPERATOR).size.times { lines << "\n" } if lines.empty?
|
32
|
+
|
33
|
+
unless lines.empty?
|
34
|
+
@semaphore.synchronize do
|
35
|
+
@device.flock(File::LOCK_EX) if device_is_file?
|
36
|
+
begin
|
37
|
+
@device.write(lines.map { |line| @formatter.call(line, timestamp) }.join("\n") + "\n")#
|
38
|
+
@buffer.write(lines.join("\n") + "\n")
|
39
|
+
ensure
|
40
|
+
@device.flock(File::LOCK_UN) if device_is_file?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def close
|
47
|
+
@device.close
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def device_is_file?
|
53
|
+
@device_is_file ||= @device.is_a?(File)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Schedule
|
2
|
+
module Notifier
|
3
|
+
class Base
|
4
|
+
def initialize(opts = {})
|
5
|
+
@options = opts
|
6
|
+
end
|
7
|
+
|
8
|
+
def notify(task, message)
|
9
|
+
@task = task
|
10
|
+
@message = message
|
11
|
+
notify!
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def notify!
|
17
|
+
raise NotImplementedError, "You must subclass this method!"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "schedule/notifier/base"
|
2
|
+
|
3
|
+
module Schedule
|
4
|
+
module Notifier
|
5
|
+
class Console < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def notify!
|
9
|
+
mail = []
|
10
|
+
mail << "From: #{@options[:from]} <#{@options[:from]}>"
|
11
|
+
mail << "To: #{@options[:to]} <#{@options[:to]}>"
|
12
|
+
mail << "Subject: #{subject}"
|
13
|
+
mail << "\n#{@message}"
|
14
|
+
mail = mail.join("\n")
|
15
|
+
|
16
|
+
puts mail
|
17
|
+
end
|
18
|
+
|
19
|
+
def subject
|
20
|
+
"[Schedule] #{@task.name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "schedule/notifier/base"
|
2
|
+
require "net/smtp"
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
module Schedule
|
6
|
+
module Notifier
|
7
|
+
class Email < Base
|
8
|
+
private
|
9
|
+
|
10
|
+
def notify!
|
11
|
+
mail = []
|
12
|
+
mail << "From: #{@options[:from]} <#{@options[:from]}>" if @options[:from]
|
13
|
+
mail << "To: #{@options[:to]} <#{@options[:to]}>"
|
14
|
+
mail << "Subject: #{subject}"
|
15
|
+
mail << "\n#{@message}"
|
16
|
+
mail = mail.join("\n")
|
17
|
+
|
18
|
+
begin
|
19
|
+
Net::SMTP.start("localhost") do |smtp|
|
20
|
+
smtp.send_message(mail, @options[:from], @options[:to])
|
21
|
+
end
|
22
|
+
rescue
|
23
|
+
mail_file = Tempfile.new("mail")
|
24
|
+
mail_file.write(mail)
|
25
|
+
mail_file.close
|
26
|
+
begin
|
27
|
+
result = system("sendmail #{@options[:to]} < #{File.expand_path(mail_file.path)}")
|
28
|
+
raise unless result
|
29
|
+
rescue => e
|
30
|
+
raise "Could not deliver email using either local SMTP or sendmail!"
|
31
|
+
ensure
|
32
|
+
mail_file.unlink rescue nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def subject
|
38
|
+
"[Schedule] #{@task.name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "schedule/logger"
|
2
|
+
|
3
|
+
module Schedule
|
4
|
+
class Runner
|
5
|
+
def initialize(task, logger = nil, notifier = nil)
|
6
|
+
@task = task
|
7
|
+
@logger = logger || Logger.new(STDOUT, @task.name)
|
8
|
+
@notifier = notifier
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
@logger.log "=== Started (#{@task.command}) ==="
|
13
|
+
|
14
|
+
status = nil
|
15
|
+
|
16
|
+
duration = with_timing do
|
17
|
+
status = execute(@task.command) do |output|
|
18
|
+
buffer = ""
|
19
|
+
begin
|
20
|
+
while (buffer << output.readpartial(1024))
|
21
|
+
if lines = buffer.slice!(/^.*[\n\r]/m)
|
22
|
+
@logger.log(lines)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue EOFError
|
26
|
+
@logger.log(buffer) unless buffer == ""
|
27
|
+
ensure
|
28
|
+
buffer = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
exit_status = if status && status.exitstatus == 0
|
34
|
+
0
|
35
|
+
elsif status && status.exitstatus
|
36
|
+
status.exitstatus
|
37
|
+
else
|
38
|
+
1
|
39
|
+
end
|
40
|
+
|
41
|
+
if exit_status == 0
|
42
|
+
@logger.log "=== Completed successfully (took #{duration} seconds) ==="
|
43
|
+
else
|
44
|
+
@logger.log "=== Failed (took #{duration} seconds) ==="
|
45
|
+
end
|
46
|
+
|
47
|
+
if @notifier
|
48
|
+
@notifier.notify(@task, @logger.buffer.string)
|
49
|
+
end
|
50
|
+
|
51
|
+
return exit_status
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# executes system command as a child process (fork + exec)
|
57
|
+
# returns stdout and stderr as a single stream
|
58
|
+
# will execute code inside block
|
59
|
+
def execute(cmd)
|
60
|
+
status = nil
|
61
|
+
|
62
|
+
begin
|
63
|
+
pipe = IO::pipe
|
64
|
+
|
65
|
+
pid = fork do
|
66
|
+
pipe[0].close
|
67
|
+
STDOUT.reopen(pipe[1])
|
68
|
+
pipe[1].close
|
69
|
+
|
70
|
+
STDERR.reopen(STDOUT)
|
71
|
+
|
72
|
+
begin
|
73
|
+
exec cmd
|
74
|
+
rescue => e
|
75
|
+
# only some ruby level errors (e.g. exec('nonexistentfile')), caught here by this rescue
|
76
|
+
# once the process is exec'd it'll take us over so any errors at that
|
77
|
+
# level will be piped to STDERR/STDOUT and dealt with in out execute block in #run
|
78
|
+
STDERR.puts "Error running command: #{e.to_s} (#{e.class.name})"#\n#{e.backtrace.map { |l| " from #{l}" }.join("\n")}"
|
79
|
+
exit(1)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
pipe[1].close
|
84
|
+
|
85
|
+
yield pipe[0]
|
86
|
+
ensure
|
87
|
+
_, status = Process.waitpid2(pid)# rescue nil
|
88
|
+
pipe.each { |io| io.close unless io.closed? }# rescue nil
|
89
|
+
end
|
90
|
+
|
91
|
+
status
|
92
|
+
end
|
93
|
+
|
94
|
+
def with_timing
|
95
|
+
started = Time.now
|
96
|
+
yield
|
97
|
+
finished = Time.now
|
98
|
+
finished - started
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
class BinaryTest < Test::Unit::TestCase
|
5
|
+
context "command runner" do
|
6
|
+
context "successful command" do
|
7
|
+
setup do
|
8
|
+
@file = Tempfile.new("log")
|
9
|
+
@path = @file.path
|
10
|
+
@file.close
|
11
|
+
@file.unlink
|
12
|
+
|
13
|
+
command_to_run_path = File.expand_path("../../commands/success", __FILE__)
|
14
|
+
@command_to_run = "#{command_to_run_path} #{@path}"
|
15
|
+
@command_runner_path = File.expand_path("../../../bin/cr", __FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "no options" do
|
19
|
+
setup do
|
20
|
+
@command = %{#{@command_runner_path} "#{@command_to_run}"}
|
21
|
+
end
|
22
|
+
|
23
|
+
should "execute command" do
|
24
|
+
silence_stream(STDOUT) do
|
25
|
+
system(@command)
|
26
|
+
assert File.exists?(@path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
should "return status code 0" do
|
31
|
+
silence_stream(STDOUT) do
|
32
|
+
assert_equal true, system(@command)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
should "log to stdout"
|
37
|
+
end
|
38
|
+
|
39
|
+
teardown do
|
40
|
+
File.unlink(@path) rescue nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "test/unit"
|
3
|
+
require "shoulda"
|
4
|
+
|
5
|
+
class Test::Unit::TestCase
|
6
|
+
# from ActiveSupport 3
|
7
|
+
def silence_stream(stream)
|
8
|
+
old_stream = stream.dup
|
9
|
+
stream.reopen(RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null")
|
10
|
+
stream.sync = true
|
11
|
+
yield
|
12
|
+
ensure
|
13
|
+
stream.reopen(old_stream)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "schedule/logger"
|
3
|
+
require "tempfile"
|
4
|
+
require "timecop"
|
5
|
+
|
6
|
+
class LoggerTest < Test::Unit::TestCase
|
7
|
+
context "creating a new logger" do
|
8
|
+
context "with the path of an existing file" do
|
9
|
+
setup do
|
10
|
+
@file = Tempfile.new("log")
|
11
|
+
@path = @file.path
|
12
|
+
|
13
|
+
@file.write "Line 1\nLine2"
|
14
|
+
@file.close
|
15
|
+
|
16
|
+
@logger = Schedule::Logger.new(@path, "")
|
17
|
+
end
|
18
|
+
|
19
|
+
should "not overwrite the file" do
|
20
|
+
assert_equal "Line 1\nLine2", File.read(@path)
|
21
|
+
end
|
22
|
+
|
23
|
+
teardown do
|
24
|
+
@file.unlink
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with the path of a non-existing file" do
|
29
|
+
setup do
|
30
|
+
file = Tempfile.new("log")
|
31
|
+
@path = file.path
|
32
|
+
file.close
|
33
|
+
file.unlink
|
34
|
+
|
35
|
+
@logger = Schedule::Logger.new(@path, "")
|
36
|
+
end
|
37
|
+
|
38
|
+
should "create the file" do
|
39
|
+
assert File.exists?(@path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with stdout" do
|
44
|
+
should "not explode" do
|
45
|
+
assert_nothing_raised { Schedule::Logger.new(STDOUT, "") }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "logging a message" do
|
51
|
+
setup do
|
52
|
+
file = Tempfile.new("log")
|
53
|
+
@path = file.path
|
54
|
+
file.close
|
55
|
+
file.unlink
|
56
|
+
|
57
|
+
@logger = Schedule::Logger.new(@path, "Awesome")
|
58
|
+
@now = Time.now
|
59
|
+
end
|
60
|
+
|
61
|
+
context "single line" do
|
62
|
+
setup do
|
63
|
+
Timecop.freeze(@now) do
|
64
|
+
@logger.log("Testing 123")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
should "write to file immediately" do
|
69
|
+
assert_match "Testing 123", File.read(@path)
|
70
|
+
end
|
71
|
+
|
72
|
+
should "prefix message with time and label" do
|
73
|
+
assert_match /^.*#{@now.strftime("%Y-%m-%d %H:%M:%S")}.*Awesome.*Testing 123.*$/, File.read(@path)
|
74
|
+
end
|
75
|
+
|
76
|
+
context "logging a subsequent message" do
|
77
|
+
setup do
|
78
|
+
@logger.log("Entry number 2")
|
79
|
+
end
|
80
|
+
|
81
|
+
should "be written to a seperate line" do
|
82
|
+
lines = File.read(@path).split(/\n/)
|
83
|
+
assert_match "Testing 123", lines[0]
|
84
|
+
assert_match "Entry number 2", lines[1]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "multiple lines" do
|
90
|
+
setup do
|
91
|
+
Timecop.freeze(@now) do
|
92
|
+
@logger.log("Multiline 1 \n\n Multiline 2\rMultiline 3 ")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
should "write as multiple, prefixed lines, preserving per-line whitespace and blank lines" do
|
97
|
+
expecting = []
|
98
|
+
expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 1 "
|
99
|
+
expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] "
|
100
|
+
expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 2"
|
101
|
+
expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 3 "
|
102
|
+
expecting << ""
|
103
|
+
expecting = expecting.join("\n")
|
104
|
+
|
105
|
+
assert_match expecting, File.read(@path)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "schedule/task"
|
3
|
+
require "schedule/runner"
|
4
|
+
require "tempfile"
|
5
|
+
|
6
|
+
class RunnerTest < Test::Unit::TestCase
|
7
|
+
context "a successful task" do
|
8
|
+
setup do
|
9
|
+
@file = Tempfile.new("log")
|
10
|
+
@path = @file.path
|
11
|
+
@file.close
|
12
|
+
|
13
|
+
@task = Schedule::Task.new("Test", "echo `pwd` > #{@path}")
|
14
|
+
@runner = Schedule::Runner.new(@task)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return 0" do
|
18
|
+
silence_stream(STDOUT) do
|
19
|
+
assert_equal 0, @runner.run
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
should "execute command" do
|
24
|
+
silence_stream(STDOUT) do
|
25
|
+
@runner.run
|
26
|
+
assert_equal `pwd`, File.read(@path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
@file.unlink
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "a non-existent command" do
|
36
|
+
setup do
|
37
|
+
@task = Schedule::Task.new("Test", "fsjfslsfdanl")
|
38
|
+
@runner = Schedule::Runner.new(@task)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "return 1" do
|
42
|
+
silence_stream(STDOUT) do
|
43
|
+
assert_equal 1, @runner.run
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "a command which uses carriage returns to seperate lines" do
|
49
|
+
setup do
|
50
|
+
@task = Schedule::Task.new("Flushing", File.expand_path("../../commands/flushing", __FILE__))
|
51
|
+
@runner = Schedule::Runner.new(@task)
|
52
|
+
end
|
53
|
+
|
54
|
+
should "return 0" do
|
55
|
+
silence_stream(STDOUT) do
|
56
|
+
assert_equal 0, @runner.run
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "a command that raises an exception" do
|
62
|
+
setup do
|
63
|
+
@file = Tempfile.new("log")
|
64
|
+
@path = @file.path
|
65
|
+
@file.close
|
66
|
+
@file.unlink
|
67
|
+
|
68
|
+
@task = Schedule::Task.new("Test", File.expand_path("../../commands/raises_exception #{@path}", __FILE__))
|
69
|
+
@runner = Schedule::Runner.new(@task)
|
70
|
+
end
|
71
|
+
|
72
|
+
should "execute command" do#
|
73
|
+
silence_stream(STDOUT) do
|
74
|
+
@runner.run
|
75
|
+
assert File.exists?(@path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
should "return 1" do
|
80
|
+
silence_stream(STDOUT) do
|
81
|
+
assert_equal 1, @runner.run
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
teardown do
|
86
|
+
File.unlink(@path) rescue nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "schedule/task"
|
3
|
+
|
4
|
+
class TaskTest < Test::Unit::TestCase
|
5
|
+
context "a new task" do
|
6
|
+
setup do
|
7
|
+
@task = Schedule::Task.new("Testing", "pwd")
|
8
|
+
end
|
9
|
+
|
10
|
+
should "return #name of Testing" do
|
11
|
+
assert_equal "Testing", @task.name
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return #command of pwd" do
|
15
|
+
assert_equal "pwd", @task.command
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schedule
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mark Dodwell
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-04-09 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: shoulda
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: timecop
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
description:
|
47
|
+
email: hi@mkdynamic.co.uk
|
48
|
+
executables:
|
49
|
+
- cr
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files: []
|
53
|
+
|
54
|
+
files:
|
55
|
+
- Rakefile
|
56
|
+
- README.md
|
57
|
+
- bin/cr
|
58
|
+
- lib/schedule/logger.rb
|
59
|
+
- lib/schedule/notifier/base.rb
|
60
|
+
- lib/schedule/notifier/console.rb
|
61
|
+
- lib/schedule/notifier/email.rb
|
62
|
+
- lib/schedule/notifier.rb
|
63
|
+
- lib/schedule/runner.rb
|
64
|
+
- lib/schedule/task.rb
|
65
|
+
- lib/schedule.rb
|
66
|
+
- test/commands/flushing
|
67
|
+
- test/commands/raises_exception
|
68
|
+
- test/commands/success
|
69
|
+
- test/integration/binary_test.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
- test/unit/logger_test.rb
|
72
|
+
- test/unit/runner_test.rb
|
73
|
+
- test/unit/task_test.rb
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/mkdynamic/schedule
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- bin
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
segments:
|
98
|
+
- 1
|
99
|
+
- 3
|
100
|
+
version: "1.3"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.3.7
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: A Ruby replacement for Cron
|
108
|
+
test_files: []
|
109
|
+
|