jandot-biorake 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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