jandot-biorake 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,122 @@
1
+ h1. Why?
2
+
3
+ There is some interest in the bioinformatics community for using rake
4
+ as a workflow tool (see e.g. this "blog post from BioinformaticsZen":http://www.bioinformaticszen.com/2008/05/organised-bioinformatics-exp
5
+ ).
6
+
7
+ Rake could be ideal for this type of work: a typical workflow will
8
+ take data and perform a first set of conversions on it (i.e. a task),
9
+ followed by a second set of conversions (that is dependent on the
10
+ first task), and so on. And obviously, bioinformaticians want to keep their data
11
+ in databases rather than files...
12
+
13
+ A typical Rakefile could look like this:
14
+ <pre>
15
+ task :001_load_data do
16
+ <load data in database>
17
+ end
18
+
19
+ task :002_calculate_averages => [:001_load_data] do
20
+ <update data in database>
21
+ end
22
+
23
+ task :003_make_histogram_of_averages => [:002_calculate_averages] do
24
+ <do stuff>
25
+ end
26
+ </pre>
27
+
28
+ The trouble is that there is no way yet to check whether a task has to
29
+ be rerun or not, because there are no timestamps. Regular rake will
30
+ rerun all three tasks from the example above, regardless if some of
31
+ them have already been completed.
32
+
33
+ BioRake adds this task timestamp functionality to rake for working with
34
+ databases. The functionality needed is very similar to the one available for
35
+ FileTasks.
36
+
37
+ So if we had reloaded the data (001), the timestamp for that task in a
38
+ metadata would be later than the one for task 002. As a result, task
39
+ 002 would automatically have to be rerun if we were to run task 003.
40
+
41
+ h1. Implementation
42
+
43
+ I've started to implement an additional type of task, called
44
+ *event*. The above snippet from a Rakefile would actually contain
45
+
46
+ <pre>
47
+ event :001_load_data do
48
+ ...
49
+ end
50
+
51
+ event :002_calculated_averages => [:001_load_data] do
52
+ ...
53
+ end
54
+ </pre>
55
+ instead of using the *task* tag.
56
+
57
+ Similar to a FileTask, *timestamps* are used to check if certain tasks
58
+ have to be re-run or not. FileTasks have the advantage that every file
59
+ has a timestamp. To implement this the metadata of event completion
60
+ times is stored in the .rake directory inside the current directory.
61
+
62
+ A *event task* automatically:
63
+
64
+ # checks the metadata to see if the task has already been run
65
+ # if so: are there any prerequisites with timestamps that are newer than the task
66
+ itself?
67
+ # (re)run the task if necessary
68
+ # update the metadata
69
+
70
+ To re-run all tasks from scratch issue a Rake::EventTask.clean or simply
71
+ <pre>
72
+ rm -rf .rake
73
+ </pre>
74
+ to reset the metadata to before any events have occured.
75
+
76
+ h1. Status
77
+
78
+ Even though the tests seem to run and I've tried some things out, I
79
+ can't guarantee production-level stability (well: call it beta). Use
80
+ at your own risk.
81
+
82
+ h1. Sample
83
+
84
+ The sample/ directory contains an example Rakefile. Suppose a
85
+ researcher has intensities for a group of individuals on a number of
86
+ probes. This information should be loaded into a database with the
87
+ tables _individuals_, _probes_ and _intensities_.
88
+
89
+ As the _intensities_ table contains foreign keys for individual and
90
+ probe, the _individuals_ and _probes_ tables have to be loaded
91
+ *before* the intensities can be loaded.
92
+
93
+ In rake-speak, this would look like:
94
+ <pre>
95
+ event :load_probes do
96
+ _load the actual data_
97
+ end
98
+
99
+ event :load_individuals do
100
+ _load the actual data_
101
+ end
102
+
103
+ event :load_intensities => [:load_probes, :load_individuals] do
104
+ _load the actual data_
105
+ end
106
+ </pre>
107
+
108
+ In a later step, the researcher might want to calculate the average
109
+ intensity per probe. This would be a new task that depends on the
110
+ intensities being loaded:
111
+
112
+ <pre>
113
+ event :calculate_averages => [:load_intensities] do
114
+ _calculate averages and store in probes table_
115
+ end
116
+ </pre>
117
+
118
+ Here, we call the database that will contain the data _sample.sqlite3_. The
119
+ _metadata_ about completed events is stored in the .rake directory.
120
+
121
+ Try a _rake -T_...
122
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ task :default => :test
2
+
3
+ desc "Run all the tests"
4
+ task :test do
5
+ Dir.chdir("test") do
6
+ ruby "test_event_task.rb"
7
+ ruby "test_event_task_timestamps.rb"
8
+ ruby "test_event_task_with_file.rb"
9
+ end
10
+ end
data/biorake.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'biorake'
3
+ s.version = "1.0"
4
+
5
+ s.authors = ["Jan Aerts","Charles Comstock"]
6
+ s.email = "jan.aerts at gmail.com"
7
+ s.homepage = "http://github.com/jandot/biorake"
8
+ s.summary = "Extension to rake to allow for timestamp usage on tasks that don't alter files"
9
+ s.description = "Extension to rake to allow for timestamp usage on tasks that don't alter files (e.g. loading data in a database)"
10
+
11
+ s.platform = Gem::Platform::RUBY
12
+ s.files = [
13
+ "biorake.gemspec",
14
+ "lib/biorake.rb",
15
+ "Rakefile",
16
+ "README.textile",
17
+ "sample/individuals.txt",
18
+ "sample/intensities.txt",
19
+ "sample/probes.txt",
20
+ "sample/Rakefile",
21
+ "test/capture_stdout.rb",
22
+ "test/event_task_creation.rb",
23
+ "test/file_creation.rb",
24
+ "test/test_event_task.rb",
25
+ "test/test_event_task_timestamps.rb",
26
+ "test/test_event_task_with_file.rb"
27
+ ]
28
+
29
+ s.add_dependency('rake')
30
+
31
+ s.has_rdoc = false
32
+
33
+ s.test_files = [
34
+ "test/test_event_task.rb",
35
+ "test/test_event_task_timestamps.rb",
36
+ "test/test_event_task_with_file.rb"
37
+ ]
38
+
39
+ s.require_path = 'lib'
40
+ s.autorequire = 'rake'
41
+
42
+ end
data/lib/biorake.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ module Rake
5
+
6
+ # #########################################################################
7
+ # An EventTask is a task that includes time based dependencies with no
8
+ # associated file. Timestamps are contained in the .rake
9
+ # directory. If any of a DataTask's prerequisites have a timestamp
10
+ # that is later than the represented by this task, then the file
11
+ # must be rebuilt (using the supplied actions).
12
+ #
13
+ class EventTask < FileTask
14
+
15
+ # Is this data task needed? Yes if it doesn't exist, or if its time stamp
16
+ # is out of date.
17
+ def needed?
18
+ return true if !File.exist?(taskfn)
19
+ return true if out_of_date?(timestamp)
20
+ false
21
+ end
22
+
23
+ # Time stamp for data task.
24
+ def timestamp
25
+ if(File.exist?(taskfn))
26
+ File.mtime(taskfn)
27
+ else
28
+ Rake::EARLY
29
+ end
30
+ end
31
+
32
+ def execute(args=nil)
33
+ super(args)
34
+ # And save the timestamp in storage
35
+ EventTask.touch(name.to_s)
36
+ end
37
+
38
+ private
39
+
40
+ def taskfn
41
+ ".rake/"+name.to_s
42
+ end
43
+
44
+ # ----------------------------------------------------------------
45
+ # Task class methods.
46
+ #
47
+ class << self
48
+ # Apply the scope to the task name according to the rules for this kind
49
+ # of task. File based tasks ignore the scope when creating the name.
50
+ def scope_name(scope, task_name)
51
+ task_name
52
+ end
53
+
54
+ def touch(fn)
55
+ FileUtils.mkdir_p ".rake"
56
+ FileUtils.touch ".rake/#{fn}"
57
+ end
58
+
59
+ def clean
60
+ FileUtils.rm_rf ".rake"
61
+ end
62
+ end
63
+ end # class Rake::EventTask
64
+ end
65
+
66
+ # Declare an event task.
67
+ #
68
+ # Example:
69
+ # event :load_probes => [:load_individuals] do
70
+ # File.open("my_file.txt").each do |line|
71
+ # probe_name, individual_id = line.chomp.split(/\t/)
72
+ # Probe.new(:name => probe_name, :individual_id => individual_id).new.save
73
+ # end
74
+ # end
75
+ def event(*args, &block)
76
+ Rake::EventTask.define_task(*args, &block)
77
+ end
data/sample/Rakefile ADDED
@@ -0,0 +1,108 @@
1
+ require '../lib/biorake.rb'
2
+ begin
3
+ require 'rubygems'
4
+ require 'dm-core'
5
+ rescue LoadError
6
+ raise LoadError, "You must have dm-core installed to run the sample (www.datamapper.org)"
7
+ end
8
+
9
+ DataMapper.setup(:default, 'sqlite3:sample.sqlite3')
10
+
11
+ class Probe
12
+ include DataMapper::Resource
13
+
14
+ property :id, Integer, :serial => true
15
+ property :name, String
16
+ property :avg, Float
17
+
18
+ has n, :intensities
19
+ has n, :individuals, :through => :intensities
20
+ end
21
+
22
+ class Individual
23
+ include DataMapper::Resource
24
+
25
+ property :id, Integer, :serial => true
26
+ property :name, String
27
+
28
+ has n, :intensities
29
+ has n, :probes, :through => :intensities
30
+ end
31
+
32
+ class Intensity
33
+ include DataMapper::Resource
34
+
35
+ property :id, Integer, :serial => true
36
+ property :probe_id, Integer
37
+ property :individual_id, Integer
38
+ property :value, Float
39
+
40
+ belongs_to :probe
41
+ belongs_to :individual
42
+ end
43
+
44
+ task :default => [:calculate_averages]
45
+
46
+ desc "Create the database"
47
+ event :create_database do
48
+ STDERR.puts "Creating the database..."
49
+ Probe.auto_migrate!
50
+ Individual.auto_migrate!
51
+ Intensity.auto_migrate!
52
+ end
53
+
54
+ desc 'Rebuild'
55
+ task :rebuild => [:clean, :calculate_averages] do
56
+ STDERR.puts "Rebuilding the whole dataset..."
57
+ end
58
+
59
+ desc "start from scratch"
60
+ task :clean do
61
+ rm_rf "sample.sqlite3"
62
+ Rake::EventTask.clean
63
+ end
64
+
65
+ desc 'Load probes from file'
66
+ event :load_probes => ['probes.txt',:create_database] do
67
+ STDERR.puts "Loading probes..."
68
+ Probe.all.destroy!
69
+ File.open('probes.txt').each do |line|
70
+ probe_name = line.chomp
71
+ Probe.new(:name => probe_name).save
72
+ end
73
+ end
74
+
75
+ desc 'Load individuals from file'
76
+ event :load_individuals => ['individuals.txt',:create_database] do
77
+ STDERR.puts "Loading individuals..."
78
+ Individual.all.destroy!
79
+ File.open('individuals.txt').each do |line|
80
+ ind_name = line.chomp
81
+ Individual.new(:name => ind_name).save
82
+ end
83
+ end
84
+
85
+ desc 'Load intensities from file'
86
+ event :load_intensities => ['intensities.txt', :load_probes, :load_individuals] do
87
+ STDERR.puts "Loading intensities..."
88
+ Intensity.all.destroy!
89
+ File.open('intensities.txt').each do |line|
90
+ probe_name, ind_name, value = line.chomp.split(/\t/)
91
+ probe = Probe.first(:name => probe_name)
92
+ ind = Individual.first(:name => ind_name)
93
+ Intensity.new(:probe_id => probe.id, :individual_id => ind.id, :value => value.to_f).save
94
+ end
95
+ end
96
+
97
+ desc 'Calculate averages for probes'
98
+ event :calculate_averages => [:load_intensities] do
99
+ STDERR.puts "Calculating averages..."
100
+ Probe.all.each do |probe|
101
+ sum = 0
102
+ probe.intensities.each do |i|
103
+ sum += i.value
104
+ end
105
+ probe.avg = sum.to_f/probe.intensities.length.to_f
106
+ probe.save
107
+ end
108
+ end
@@ -0,0 +1,5 @@
1
+ ind_1
2
+ ind_2
3
+ ind_3
4
+ ind_4
5
+ ind_5
@@ -0,0 +1,25 @@
1
+ probe_1 ind_1 836
2
+ probe_1 ind_2 729
3
+ probe_1 ind_3 872
4
+ probe_1 ind_4 534
5
+ probe_1 ind_5 739
6
+ probe_2 ind_1 152
7
+ probe_2 ind_2 630
8
+ probe_2 ind_3 826
9
+ probe_2 ind_4 917
10
+ probe_2 ind_5 1027
11
+ probe_3 ind_1 710
12
+ probe_3 ind_2 720
13
+ probe_3 ind_3 891
14
+ probe_3 ind_4 710
15
+ probe_3 ind_5 280
16
+ probe_4 ind_1 820
17
+ probe_4 ind_2 720
18
+ probe_4 ind_3 918
19
+ probe_4 ind_4 812
20
+ probe_4 ind_5 710
21
+ probe_5 ind_1 928
22
+ probe_5 ind_2 817
23
+ probe_5 ind_3 918
24
+ probe_5 ind_4 710
25
+ probe_5 ind_5 918
data/sample/probes.txt ADDED
@@ -0,0 +1,5 @@
1
+ probe_1
2
+ probe_2
3
+ probe_3
4
+ probe_4
5
+ probe_5
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'stringio'
4
+
5
+ # Mix-in for capturing standard output.
6
+ module CaptureStdout
7
+ def capture_stdout
8
+ s = StringIO.new
9
+ oldstdout = $stdout
10
+ $stdout = s
11
+ yield
12
+ s.string
13
+ ensure
14
+ $stdout = oldstdout
15
+ end
16
+
17
+ def capture_stderr
18
+ s = StringIO.new
19
+ oldstderr = $stderr
20
+ $stderr = s
21
+ yield
22
+ s.string
23
+ ensure
24
+ $stderr = oldstderr
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module EventTaskCreation
2
+
3
+ def create_timed_event_tasks(old_task, *new_tasks)
4
+ return if File.exist?(".rake/"+old_task.to_s) && new_tasks.all? { |new_task| File.exist?(new_task) }
5
+
6
+ old_time = create_task(old_task)
7
+ new_tasks.each do |new_task|
8
+ while create_task(new_task) <= old_time + 0.00001
9
+ sleep(0.5)
10
+ delete_task(new_task.to_s)
11
+ end
12
+ end
13
+ end
14
+
15
+ def create_task(name)
16
+ Rake::EventTask.touch(name.to_s)
17
+ return File.mtime(".rake/"+name.to_s)
18
+ end
19
+
20
+ def delete_task(name)
21
+ File.delete(".rake/"+name.to_s) rescue nil
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module FileCreation
4
+ OLDFILE = "testdata/old"
5
+ NEWFILE = "testdata/new"
6
+
7
+ def create_timed_files(oldfile, *newfiles)
8
+ return if File.exist?(oldfile) && newfiles.all? { |newfile| File.exist?(newfile) }
9
+ old_time = create_file(oldfile)
10
+ newfiles.each do |newfile|
11
+ while create_file(newfile) <= old_time
12
+ sleep(0.1)
13
+ File.delete(newfile) rescue nil
14
+ end
15
+ end
16
+ end
17
+
18
+ def create_dir(dirname)
19
+ FileUtils.mkdir_p(dirname) unless File.exist?(dirname)
20
+ File.stat(dirname).mtime
21
+ end
22
+
23
+ def create_file(name)
24
+ create_dir(File.dirname(name))
25
+ FileUtils.touch(name) unless File.exist?(name)
26
+ File.stat(name).mtime
27
+ end
28
+
29
+ def delete_file(name)
30
+ File.delete(name) rescue nil
31
+ end
32
+ end
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+
4
+ require 'test/unit'
5
+ #require 'fileutils'
6
+ require '../lib/biorake'
7
+ require 'file_creation.rb'
8
+ require 'capture_stdout.rb'
9
+ require 'event_task_creation'
10
+
11
+
12
+ module Interning
13
+ private
14
+
15
+ def intern(name, *args)
16
+ Rake.application.define_task(Rake::EventTask, name, *args)
17
+ end
18
+ end
19
+
20
+ ######################################################################
21
+ class EventTestTask < Test::Unit::TestCase
22
+ include CaptureStdout
23
+ include Rake
24
+ include Interning
25
+ include EventTaskCreation
26
+
27
+ def setup
28
+ EventTask.clear
29
+ end
30
+
31
+ def test_create
32
+ arg = nil
33
+ t = intern(:name).enhance { |task| arg = task; 1234 }
34
+ assert_equal "name", t.name
35
+ assert_equal [], t.prerequisites
36
+ assert t.prerequisites.is_a?(FileList)
37
+ assert t.needed?
38
+ t.execute(0)
39
+ assert_equal t, arg
40
+ assert_nil t.source
41
+ assert_equal [], t.sources
42
+ end
43
+
44
+ def test_inspect
45
+ t = intern(:foo).enhance([:bar, :baz])
46
+ assert_equal "<Rake::EventTask foo => [bar, baz]>", t.inspect
47
+ end
48
+
49
+ def test_invoke
50
+ runlist = []
51
+ t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
52
+ t2 = intern(:t2).enhance { |t| runlist << t.name }
53
+ t3 = intern(:t3).enhance { |t| runlist << t.name }
54
+ assert_equal [:t2, :t3], t1.prerequisites
55
+ t1.invoke
56
+ assert_equal ["t2", "t3", "t1"], runlist
57
+ end
58
+
59
+ def test_invoke_with_circular_dependencies
60
+ runlist = []
61
+ t1 = intern(:t1).enhance([:t2]) { |t| runlist << t.name; 3321 }
62
+ t2 = intern(:t2).enhance([:t1]) { |t| runlist << t.name }
63
+ assert_equal [:t2], t1.prerequisites
64
+ assert_equal [:t1], t2.prerequisites
65
+ ex = assert_raise RuntimeError do
66
+ t1.invoke
67
+ end
68
+ assert_match(/circular dependency/i, ex.message)
69
+ assert_match(/t1 => t2 => t1/, ex.message)
70
+ end
71
+
72
+ def test_dry_run_prevents_actions
73
+ Rake.application.options.dryrun = true
74
+ runlist = []
75
+ t1 = intern(:t1).enhance { |t| runlist << t.name; 3321 }
76
+ out = capture_stdout { t1.invoke }
77
+ assert_match(/execute .*t1/i, out)
78
+ assert_match(/dry run/i, out)
79
+ assert_no_match(/invoke/i, out)
80
+ assert_equal [], runlist
81
+ ensure
82
+ Rake.application.options.dryrun = false
83
+ end
84
+
85
+ def test_tasks_can_be_traced
86
+ Rake.application.options.trace = true
87
+ t1 = intern(:t1) { |t| runlist << t.name; 3321 }
88
+ out = capture_stdout {
89
+ t1.invoke
90
+ }
91
+ assert_match(/invoke t1/i, out)
92
+ assert_match(/execute t1/i, out)
93
+ ensure
94
+ Rake.application.options.trace = false
95
+ end
96
+
97
+ def test_no_double_invoke
98
+ runlist = []
99
+ t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
100
+ t2 = intern(:t2).enhance([:t3]) { |t| runlist << t.name }
101
+ t3 = intern(:t3).enhance { |t| runlist << t.name }
102
+ t1.invoke
103
+ assert_equal ["t3", "t2", "t1"], runlist
104
+ end
105
+
106
+ def test_find
107
+ event :tfind
108
+ assert_equal "tfind", EventTask[:tfind].name
109
+ ex = assert_raise(RuntimeError) { EventTask[:leaves] }
110
+ assert_equal "Don't know how to build task 'leaves'", ex.message
111
+ delete_task(:tfind)
112
+ end
113
+
114
+ def test_defined
115
+ assert ! EventTask.task_defined?(:a)
116
+ event :a
117
+ assert EventTask.task_defined?(:a)
118
+ delete_task :a
119
+ end
120
+
121
+ def test_multi_invocations
122
+ runs = []
123
+ p = proc do |t| runs << t.name end
124
+ event({:t1=>[:t2,:t3]}, &p)
125
+ event({:t2=>[:t3]}, &p)
126
+ event(:t3, &p)
127
+ EventTask[:t1].invoke
128
+ assert_equal ["t1", "t2", "t3"], runs.sort
129
+ delete_task :t1
130
+ delete_task :t2
131
+ delete_task :t3
132
+ end
133
+
134
+ def test_task_list
135
+ task :t2
136
+ task :t1 => [:t2]
137
+ assert_equal ["t1", "t2"], Task.tasks.collect {|t| t.name}
138
+ end
139
+
140
+ def test_task_gives_name_on_to_s
141
+ task :abc
142
+ assert_equal "abc", Task[:abc].to_s
143
+ end
144
+
145
+ def test_symbols_can_be_prerequisites
146
+ task :a => :b
147
+ assert_equal ["b"], Task[:a].prerequisites
148
+ end
149
+
150
+ def test_strings_can_be_prerequisites
151
+ task :a => "b"
152
+ assert_equal ["b"], Task[:a].prerequisites
153
+ end
154
+
155
+ def test_arrays_can_be_prerequisites
156
+ task :a => ["b", "c"]
157
+ assert_equal ["b", "c"], Task[:a].prerequisites
158
+ end
159
+
160
+ def test_filelists_can_be_prerequisites
161
+ task :a => FileList.new.include("b", "c")
162
+ assert_equal ["b", "c"], Task[:a].prerequisites
163
+ end
164
+
165
+ def test_investigation_output
166
+ t1 = intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
167
+ intern(:t2)
168
+ intern(:t3)
169
+ out = t1.investigation
170
+ assert_match(/class:\s*Rake::EventTask/, out)
171
+ assert_match(/needed:\s*true/, out)
172
+ assert_match(/pre-requisites:\s*--t2/, out)
173
+ end
174
+
175
+
176
+ def test_extended_comments
177
+ desc %{
178
+ This is a comment.
179
+
180
+ And this is the extended comment.
181
+ name -- Name of task to execute.
182
+ rev -- Software revision to use.
183
+ }
184
+ t = intern(:t, :name, :rev)
185
+ assert_equal "[name,rev]", t.arg_description
186
+ assert_equal "This is a comment.", t.comment
187
+ assert_match(/^\s*name -- Name/, t.full_comment)
188
+ assert_match(/^\s*rev -- Software/, t.full_comment)
189
+ assert_match(/\A\s*This is a comment\.$/, t.full_comment)
190
+ end
191
+
192
+ def test_multiple_comments
193
+ desc "line one"
194
+ t = intern(:t)
195
+ desc "line two"
196
+ intern(:t)
197
+ assert_equal "line one / line two", t.comment
198
+ end
199
+
200
+ def test_settable_comments
201
+ t = intern(:t)
202
+ t.comment = "HI"
203
+ assert_equal "HI", t.comment
204
+ end
205
+
206
+ def teardown
207
+ EventTask.clean
208
+ end
209
+
210
+ end
211
+
212
+ #####################################################################
213
+ class TestTaskWithArguments < Test::Unit::TestCase
214
+ include CaptureStdout
215
+ include Rake
216
+ include Interning
217
+ include EventTaskCreation
218
+
219
+ def setup
220
+ EventTask.clear
221
+ end
222
+
223
+ def test_no_args_given
224
+ t = event :t
225
+ assert_equal [], t.arg_names
226
+ delete_task :t
227
+ end
228
+
229
+ def test_args_given
230
+ t = event :t, :a, :b
231
+ assert_equal [:a, :b], t.arg_names
232
+ delete_task :t
233
+ end
234
+
235
+ def test_name_and_needs
236
+ t = event(:t => [:pre])
237
+ assert_equal "t", t.name
238
+ assert_equal [], t.arg_names
239
+ assert_equal ["pre"], t.prerequisites
240
+ delete_task :t
241
+ delete_task :pre
242
+ end
243
+
244
+ def test_name_and_explicit_needs
245
+ t = event(:t, :needs => [:pre])
246
+ assert_equal "t", t.name
247
+ assert_equal [], t.arg_names
248
+ assert_equal ["pre"], t.prerequisites
249
+ delete_task :t
250
+ end
251
+
252
+ def test_name_args_and_explicit_needs
253
+ t = event(:t, :x, :y, :needs => [:pre])
254
+ assert_equal "t", t.name
255
+ assert_equal [:x, :y], t.arg_names
256
+ assert_equal ["pre"], t.prerequisites
257
+ delete_task :t
258
+ delete_task :pre
259
+ end
260
+
261
+ def test_illegal_keys_in_task_name_hash
262
+ assert_raise RuntimeError do
263
+ t = event(:t, :x, :y => 1, :needs => [:pre])
264
+ end
265
+ delete_task :pre
266
+ end
267
+
268
+ def test_arg_list_is_empty_if_no_args_given
269
+ t = intern(:t).enhance do |tt, args|
270
+ assert_equal({}, args.to_hash)
271
+ end
272
+ t.invoke(1, 2, 3)
273
+ end
274
+
275
+ def test_tasks_can_access_arguments_as_hash
276
+ t = event :t, :a, :b, :c do |tt, args|
277
+ assert_equal({:a => 1, :b => 2, :c => 3}, args.to_hash)
278
+ assert_equal 1, args[:a]
279
+ assert_equal 2, args[:b]
280
+ assert_equal 3, args[:c]
281
+ assert_equal 1, args.a
282
+ assert_equal 2, args.b
283
+ assert_equal 3, args.c
284
+ end
285
+ t.invoke(1, 2, 3)
286
+ delete_task :t
287
+ end
288
+
289
+ def test_actions_of_various_arity_are_ok_with_args
290
+ notes = []
291
+ t = intern(:t, :x).enhance do
292
+ notes << :a
293
+ end
294
+ t.enhance do | |
295
+ notes << :b
296
+ end
297
+ t.enhance do |task|
298
+ notes << :c
299
+ assert_kind_of EventTask, task
300
+ end
301
+ t.enhance do |t2, args|
302
+ notes << :d
303
+ assert_equal t, t2
304
+ assert_equal({:x => 1}, args.to_hash)
305
+ end
306
+ assert_nothing_raised do t.invoke(1) end
307
+ assert_equal [:a, :b, :c, :d], notes
308
+ end
309
+
310
+ def test_arguments_are_passed_to_block
311
+ t = intern(:t, :a, :b).enhance { |tt, args|
312
+ assert_equal( { :a => 1, :b => 2 }, args.to_hash )
313
+ }
314
+ t.invoke(1, 2)
315
+ end
316
+
317
+ def test_extra_parameters_are_ignored
318
+ t = intern(:t, :a).enhance { |tt, args|
319
+ assert_equal 1, args.a
320
+ assert_nil args[2]
321
+ }
322
+ t.invoke(1, 2)
323
+ end
324
+
325
+ def test_arguments_are_passed_to_all_blocks
326
+ counter = 0
327
+ t = event :t, :a
328
+ event :t do |tt, args|
329
+ assert_equal 1, args.a
330
+ counter += 1
331
+ end
332
+ event :t do |tt, args|
333
+ assert_equal 1, args.a
334
+ counter += 1
335
+ end
336
+ t.invoke(1)
337
+ assert_equal 2, counter
338
+ delete_task :t
339
+ end
340
+
341
+ def test_block_with_no_parameters_is_ok
342
+ t = intern(:t).enhance { }
343
+ t.invoke(1, 2)
344
+ end
345
+
346
+ def test_name_with_args
347
+ desc "T"
348
+ t = intern(:tt, :a, :b)
349
+ assert_equal "tt", t.name
350
+ assert_equal "T", t.comment
351
+ assert_equal "[a,b]", t.arg_description
352
+ assert_equal "tt[a,b]", t.name_with_args
353
+ assert_equal [:a, :b],t.arg_names
354
+ end
355
+
356
+ def test_named_args_are_passed_to_prereqs
357
+ value = nil
358
+ pre = intern(:pre, :rev).enhance { |t, args| value = args.rev }
359
+ t = intern(:t, :name, :rev).enhance([:pre])
360
+ t.invoke("bill", "1.2")
361
+ assert_equal "1.2", value
362
+ end
363
+
364
+ def test_args_not_passed_if_no_prereq_names
365
+ pre = intern(:pre).enhance { |t, args|
366
+ assert_equal({}, args.to_hash)
367
+ assert_equal "bill", args.name
368
+ }
369
+ t = intern(:t, :name, :rev).enhance([:pre])
370
+ t.invoke("bill", "1.2")
371
+ end
372
+
373
+ def test_args_not_passed_if_no_arg_names
374
+ pre = intern(:pre, :rev).enhance { |t, args|
375
+ assert_equal({ :rev => nil }, args.to_hash)
376
+ }
377
+ t = intern(:t).enhance([:pre])
378
+ t.invoke("bill", "1.2")
379
+ end
380
+
381
+ def teardown
382
+ EventTask.clean
383
+ end
384
+ end
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test/unit'
4
+ require '../lib/biorake'
5
+ require 'event_task_creation'
6
+ require 'yaml'
7
+
8
+ class TestEventTaskTimeStamp < Test::Unit::TestCase
9
+ include Rake
10
+ include EventTaskCreation
11
+
12
+ def setup
13
+ EventTask.clear
14
+ @runs = Array.new
15
+ end
16
+
17
+ def test_names
18
+ event :names_1
19
+ event :names_2 => :names_1
20
+ event_task_1 = EventTask[:names_1]
21
+ event_task_2 = EventTask[:names_2]
22
+ assert_equal 'names_1', event_task_1.name
23
+ assert_equal 'names_2', event_task_2.name
24
+ end
25
+
26
+ def test_event_need
27
+ name_1 = :event_need_1
28
+ event name_1
29
+ event_task = EventTask[name_1]
30
+ assert_equal name_1.to_s, event_task.name
31
+
32
+ name_2 = :event_need_2
33
+ event name_2
34
+ event_task = EventTask[name_2]
35
+ assert event_task.needed?, 'task should be needed'
36
+
37
+ EventTask.touch(name_2.to_s)
38
+ assert_equal nil, event_task.prerequisites.collect{|n| EventTask[n].timestamp}.max
39
+ assert ! event_task.needed?, "task should not be needed"
40
+ end
41
+
42
+ def test_task_times_new_depends_on_old
43
+ create_timed_event_tasks(:old_task_times_new_depends_on_old, :new_task_times_new_depends_on_old)
44
+
45
+ t1 = Rake.application.intern(EventTask, :new_task_times_new_depends_on_old).enhance([:old_task_times_new_depends_on_old])
46
+ t2 = Rake.application.intern(EventTask, :old_task_times_new_depends_on_old)
47
+ assert ! t2.needed?, "Should not need to build old task"
48
+ assert ! t1.needed?, "Should not need to rebuild new task because of old"
49
+ end
50
+
51
+ def test_task_times_old_depends_on_new
52
+ create_timed_event_tasks(:old_task_times_old_depends_on_new, :new_task_times_old_depends_on_new)
53
+
54
+ t1 = Rake.application.intern(EventTask, :old_task_times_old_depends_on_new).enhance([:new_task_times_old_depends_on_new])
55
+ t2 = Rake.application.intern(EventTask, :new_task_times_old_depends_on_new)
56
+ assert ! t2.needed?, "Should not need to build new task"
57
+ preq_stamp = t1.prerequisites.collect{|t| EventTask[t].timestamp}.max
58
+ assert_equal t2.timestamp, preq_stamp
59
+ assert t1.timestamp < preq_stamp, "T1 should be older"
60
+ assert t1.needed?, "Should need to rebuild old task because of new"
61
+ end
62
+
63
+ def test_event_depends_on_task_depend_on_data
64
+ create_timed_event_tasks(:old_event_depends_on_task_depend_on_event, :new_event_depends_on_task_depend_on_event)
65
+
66
+ event :new_event_depends_on_task_depend_on_event => [:obj] do |t| @runs << t.name end
67
+ task :obj => [:old_event_depends_on_task_depend_on_event] do |t| @runs << t.name end
68
+ event :old_event_depends_on_task_depend_on_event do |t| @runs << t.name end
69
+
70
+ Task[:obj].invoke
71
+ EventTask[:new_event_depends_on_task_depend_on_event].invoke
72
+ assert ! @runs.include?(:new_event_depends_on_task_depend_on_event)
73
+ end
74
+
75
+ def test_existing_event_task_depends_on_non_existing_event_task
76
+ create_task(:old_existing_event_task_depends_on_non_existing_event_task)
77
+ delete_task(:new_existing_event_task_depends_on_non_existing_event_task)
78
+ event :new_existing_event_task_depends_on_non_existing_event_task
79
+ event :old_existing_event_task_depends_on_non_existing_event_task => :new_existing_event_task_depends_on_non_existing_event_task
80
+ assert_nothing_raised do EventTask[:old_existing_event_task_depends_on_non_existing_event_task].invoke end
81
+ end
82
+
83
+ def teardown
84
+ EventTask.clean
85
+ end
86
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+
4
+ require 'test/unit'
5
+ #require 'fileutils'
6
+ require '../lib/biorake'
7
+ require 'file_creation.rb'
8
+ require 'capture_stdout.rb'
9
+ require 'event_task_creation'
10
+
11
+
12
+ module Interning
13
+ private
14
+
15
+ def event_intern(name, *args)
16
+ Rake.application.define_task(Rake::EventTask, name, *args)
17
+ end
18
+
19
+ def file_intern(name, *args)
20
+ Rake.application.define_task(Rake::FileTask, name, *args)
21
+ end
22
+
23
+ end
24
+
25
+ ######################################################################
26
+ class EventTestTask < Test::Unit::TestCase
27
+ include CaptureStdout
28
+ include Rake
29
+ include Interning
30
+ include EventTaskCreation
31
+
32
+ def setup
33
+ EventTask.clear
34
+ FileTask.clear
35
+ end
36
+
37
+ def test_invoke
38
+ runlist = []
39
+ t1 = event_intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
40
+ t2 = file_intern(:t2).enhance { |t| runlist << t.name }
41
+ t3 = event_intern(:t3).enhance { |t| runlist << t.name }
42
+ assert_equal [:t2, :t3], t1.prerequisites
43
+ t1.invoke
44
+ assert_equal ["t2", "t3", "t1"], runlist
45
+ end
46
+
47
+ def test_invoke_with_circular_dependencies
48
+ runlist = []
49
+ t1 = event_intern(:t1).enhance([:t2]) { |t| runlist << t.name; 3321 }
50
+ t2 = file_intern(:t2).enhance([:t1]) { |t| runlist << t.name }
51
+ assert_equal [:t2], t1.prerequisites
52
+ assert_equal [:t1], t2.prerequisites
53
+ ex = assert_raise RuntimeError do
54
+ t1.invoke
55
+ end
56
+ assert_match(/circular dependency/i, ex.message)
57
+ assert_match(/t1 => t2 => t1/, ex.message)
58
+ end
59
+
60
+ def test_no_double_invoke
61
+ runlist = []
62
+ t1 = event_intern(:t1).enhance([:t2, :t3]) { |t| runlist << t.name; 3321 }
63
+ t2 = event_intern(:t2).enhance([:t3]) { |t| runlist << t.name }
64
+ t3 = event_intern(:t3).enhance { |t| runlist << t.name }
65
+ t1.invoke
66
+ assert_equal ["t3", "t2", "t1"], runlist
67
+ end
68
+
69
+ def teardown
70
+ Rake::EventTask.clean
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jandot-biorake
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Jan Aerts
8
+ - Charles Comstock
9
+ autorequire: rake
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-08-13 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rake
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Extension to rake to allow for timestamp usage on tasks that don't alter files (e.g. loading data in a database)
26
+ email: jan.aerts at gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - biorake.gemspec
35
+ - lib/biorake.rb
36
+ - Rakefile
37
+ - README.textile
38
+ - sample/individuals.txt
39
+ - sample/intensities.txt
40
+ - sample/probes.txt
41
+ - sample/Rakefile
42
+ - test/capture_stdout.rb
43
+ - test/event_task_creation.rb
44
+ - test/file_creation.rb
45
+ - test/test_event_task.rb
46
+ - test/test_event_task_timestamps.rb
47
+ - test/test_event_task_with_file.rb
48
+ has_rdoc: false
49
+ homepage: http://github.com/jandot/biorake
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Extension to rake to allow for timestamp usage on tasks that don't alter files
74
+ test_files:
75
+ - test/test_event_task.rb
76
+ - test/test_event_task_timestamps.rb
77
+ - test/test_event_task_with_file.rb