autoflow 0.5.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +8 -0
- data/bin/AutoFlow +30 -21
- data/lib/autoflow/batch.rb +200 -78
- data/lib/autoflow/queue_manager.rb +2 -0
- data/lib/autoflow/stack.rb +65 -106
- data/lib/autoflow/version.rb +1 -1
- data/test/stack_test.rb +148 -0
- data/test/test_helper.rb +11 -0
- metadata +7 -3
data/Rakefile
CHANGED
data/bin/AutoFlow
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
ROOT_PATH=File.dirname(__FILE__)
|
4
|
-
$: << File.expand_path(File.join(ROOT_PATH, "
|
5
|
-
$: << File.expand_path(File.join(ROOT_PATH, "
|
6
|
-
$: << File.expand_path(File.join(ROOT_PATH, "
|
4
|
+
$: << File.expand_path(File.join(ROOT_PATH, "..", "lib"))
|
5
|
+
$: << File.expand_path(File.join(ROOT_PATH, "..", "lib", "autoflow"))
|
6
|
+
$: << File.expand_path(File.join(ROOT_PATH, "..", "lib", "autoflow", "queue_managers"))
|
7
7
|
|
8
8
|
require 'optparse'
|
9
9
|
require 'autoflow'
|
@@ -21,8 +21,8 @@ def get_templates(string_template)
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def get_repositories
|
24
|
-
directories = []
|
25
|
-
directories
|
24
|
+
directories = [File.join(ENV['HOME'], 'autoflow_templates')]
|
25
|
+
directories.concat(ENV['WORKFLOW_REPOSITORY'].split(':')) if !ENV['WORKFLOW_REPOSITORY'].nil?
|
26
26
|
return directories
|
27
27
|
end
|
28
28
|
|
@@ -33,11 +33,9 @@ end
|
|
33
33
|
|
34
34
|
def list_repository_templates
|
35
35
|
templates = []
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
templates.concat(Dir.entries(dir))
|
40
|
-
end
|
36
|
+
directories = get_repositories
|
37
|
+
directories.each do |dir|
|
38
|
+
templates.concat(Dir.entries(dir))
|
41
39
|
end
|
42
40
|
templates.delete('.')
|
43
41
|
templates.delete('..')
|
@@ -73,7 +71,7 @@ options = {}
|
|
73
71
|
template_file = ''
|
74
72
|
optparse = OptionParser.new do |opts|
|
75
73
|
options[:add] = nil
|
76
|
-
opts.on( '-a', '--add STRING', '
|
74
|
+
opts.on( '-a', '--add STRING', 'Put a copy of any selected workflow template in repository' ) do |add|
|
77
75
|
options[:add] = add
|
78
76
|
end
|
79
77
|
|
@@ -83,22 +81,27 @@ optparse = OptionParser.new do |opts|
|
|
83
81
|
end
|
84
82
|
|
85
83
|
options[:cpus] = 16
|
86
|
-
opts.on( '-c', '--cpus INTEGER', 'Max
|
84
|
+
opts.on( '-c', '--cpus INTEGER', 'Max number of CPUs that can be used in all workflow' ) do |cpus|
|
87
85
|
options[:cpus] = cpus.to_i
|
88
86
|
end
|
89
87
|
|
88
|
+
options[:comment] = FALSE
|
89
|
+
opts.on( '-C', '--comment_main_command', 'Comment first line of main command job' ) do
|
90
|
+
options[:comment] = TRUE
|
91
|
+
end
|
92
|
+
|
90
93
|
options[:external_dependencies] = []
|
91
|
-
opts.on( '-d', '--external_dependencies STRING', 'The workflow will start when indicated jobs
|
94
|
+
opts.on( '-d', '--external_dependencies STRING', 'The workflow will start when indicated jobs are finished on queue system. Format: \'id1,id2,id3..\'') do |external_dependencies|
|
92
95
|
options[:external_dependencies] = external_dependencies.split(',')
|
93
96
|
end
|
94
97
|
|
95
98
|
options[:retry] = FALSE
|
96
|
-
opts.on( '-f', '--force', 'Execute all jobs,
|
99
|
+
opts.on( '-f', '--force', 'Execute all jobs, including any job commented with %' ) do
|
97
100
|
options[:retry] = TRUE
|
98
101
|
end
|
99
102
|
|
100
103
|
options[:graph] = nil
|
101
|
-
opts.on( '-g', '--graph STRING', 'Draw the template.
|
104
|
+
opts.on( '-g', '--graph STRING', 'Draw a chart for the template. The workflow is not executed \'t\' use TIDs for box names \'f\' use folder names for boxes.' ) do |graph|
|
102
105
|
options[:graph] = graph
|
103
106
|
end
|
104
107
|
|
@@ -108,17 +111,17 @@ optparse = OptionParser.new do |opts|
|
|
108
111
|
end
|
109
112
|
|
110
113
|
options[:key_name] = FALSE
|
111
|
-
opts.on( '-k', '--use_key_name', 'Use job names
|
114
|
+
opts.on( '-k', '--use_key_name', ' Use job names as folder names' ) do
|
112
115
|
options[:key_name] = TRUE
|
113
116
|
end
|
114
117
|
|
115
118
|
options[:list] = nil
|
116
|
-
opts.on( 'l', '--list_repository STRING', 'List template names in repository') do |name|
|
119
|
+
opts.on( '-l', '--list_repository STRING', 'List template names in repository') do |name|
|
117
120
|
options[:list] = name
|
118
121
|
end
|
119
122
|
|
120
123
|
options[:memory] = '4gb'
|
121
|
-
opts.on( '-m', '--memory STRING', 'Max memory can be
|
124
|
+
opts.on( '-m', '--memory STRING', 'Max memory that can be allocated in a task' ) do |mem|
|
122
125
|
options[:memory] = mem
|
123
126
|
end
|
124
127
|
|
@@ -128,7 +131,7 @@ optparse = OptionParser.new do |opts|
|
|
128
131
|
end
|
129
132
|
|
130
133
|
options[:output] = 'exec'
|
131
|
-
opts.on( '-o', '--output STRING', '
|
134
|
+
opts.on( '-o', '--output STRING', 'Define an output folder name' ) do |output|
|
132
135
|
options[:output] = output
|
133
136
|
end
|
134
137
|
|
@@ -151,7 +154,7 @@ optparse = OptionParser.new do |opts|
|
|
151
154
|
end
|
152
155
|
|
153
156
|
options[:use_ntasks] = FALSE
|
154
|
-
opts.on( '-s', '--use_ntasks', 'Use
|
157
|
+
opts.on( '-s', '--use_ntasks', 'Use several nodes on execution' ) do
|
155
158
|
options[:use_ntasks] = TRUE
|
156
159
|
end
|
157
160
|
|
@@ -167,7 +170,7 @@ optparse = OptionParser.new do |opts|
|
|
167
170
|
|
168
171
|
|
169
172
|
options[:verbose] = FALSE
|
170
|
-
opts.on( '-v', '--verbose', 'Show info without
|
173
|
+
opts.on( '-v', '--verbose', 'Show info without launching jobs' ) do
|
171
174
|
options[:verbose] = TRUE
|
172
175
|
end
|
173
176
|
|
@@ -200,6 +203,11 @@ optparse.parse!
|
|
200
203
|
#################################################################################################
|
201
204
|
# MAIN
|
202
205
|
#################################################################################################
|
206
|
+
#Create repository
|
207
|
+
if !File.exists?(File.join(ENV['HOME'], 'autoflow_templates'))
|
208
|
+
Dir.mkdir(File.join(ENV['HOME'], 'autoflow_templates'))
|
209
|
+
end
|
210
|
+
|
203
211
|
# List templates
|
204
212
|
templates_rep_names = list_repository_templates
|
205
213
|
if !options[:list].nil?
|
@@ -295,6 +303,7 @@ if !options[:graph].nil?
|
|
295
303
|
stack.draw(template_file, options[:graph])
|
296
304
|
else
|
297
305
|
stack.inspect if options[:verbose]
|
306
|
+
stack.comment_main_command if options[:comment]
|
298
307
|
manager.exec
|
299
308
|
end
|
300
309
|
options[:ssh].close if options[:remote]
|
data/lib/autoflow/batch.rb
CHANGED
@@ -2,7 +2,7 @@ class Batch
|
|
2
2
|
attr_accessor :name, :iterator, :dependencies, :init, :main_command, :attrib, :id, :jobs, :parent
|
3
3
|
|
4
4
|
@@all_batch = {}
|
5
|
-
@@
|
5
|
+
@@jobs_names = []
|
6
6
|
@@batch_iterator_relations = {}
|
7
7
|
@@state_iterations = {}
|
8
8
|
@@general_computation_attrib = {
|
@@ -13,21 +13,14 @@ class Batch
|
|
13
13
|
:multinode => nil,
|
14
14
|
:ntask => nil
|
15
15
|
}
|
16
|
-
|
17
|
-
|
18
|
-
def self.set_job_folder_definition(definition)
|
19
|
-
@@folder_name = definition
|
20
|
-
end
|
21
|
-
|
16
|
+
|
22
17
|
def self.set_general_attrib(attrib_hash)
|
23
18
|
@@general_computation_attrib = attrib_hash
|
24
19
|
end
|
25
20
|
|
26
|
-
def self.get_job_relations
|
27
|
-
return @@all_jobs_relations
|
28
|
-
end
|
29
21
|
|
30
22
|
def initialize(tag, init, main_command, id, exec_folder)
|
23
|
+
replace_regexp(tag, init, main_command)
|
31
24
|
@name = nil
|
32
25
|
@id = id
|
33
26
|
@iterator = [nil]
|
@@ -54,40 +47,121 @@ class Batch
|
|
54
47
|
@@all_batch[@name] = self
|
55
48
|
end
|
56
49
|
|
50
|
+
def replace_regexp(tag, init, main_command)
|
51
|
+
scan_JobRegExp_tag(tag) if tag.include?('JobRegExp:')
|
52
|
+
[init, main_command].each do |intructions|
|
53
|
+
if intructions.class.to_s == 'String'
|
54
|
+
while intructions.include?('!JobRegExp:')
|
55
|
+
scan_JobRegExp(intructions)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
#puts main_command.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
def scan_JobRegExp(command)
|
63
|
+
data = /!JobRegExp:([^ \n]+):([^ \n]+)!([^ \n]+)/.match(command) # *to1 with regexp
|
64
|
+
#data[0] => reference string (command), data[1] => batch_pattern, data[2] => iterator_pattern, data[3] => adyacent string to regexp as regexp/file_name
|
65
|
+
job_names = get_dependencies_by_regexp(data[1], data[2])
|
66
|
+
new_string = job_names.map{|jn| jn + ')' + data[3] }.join(' ')
|
67
|
+
command.gsub!(data[0], new_string)
|
68
|
+
#puts command.inspect
|
69
|
+
end
|
70
|
+
|
71
|
+
def scan_JobRegExp_tag(tag)
|
72
|
+
data = /JobRegExp:([^ \n]+):([^;\] \n]+)/.match(tag) # 1to1 with regexp
|
73
|
+
#data[0] => reference string (command), data[1] => batch_pattern, data[2] => iterator_pattern
|
74
|
+
job_names = get_dependencies_by_regexp(data[1], data[2])
|
75
|
+
new_string = job_names.map{|jn| jn + ')'}.join(';')
|
76
|
+
tag.gsub!(data[0], new_string)
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_dependencies_by_regexp(batch_pattern, iterator_pattern)
|
80
|
+
selected_batches = @@all_batch.keys.select{|cmd_name| cmd_name =~ /#{batch_pattern}/}
|
81
|
+
job_names = []
|
82
|
+
selected_batches.each do |batch_name|
|
83
|
+
iterators = @@all_batch[batch_name].iterator
|
84
|
+
iterators = iterators.map{|it| it.gsub(/&|\!|\%|\)/,'')} if !iterators.first.nil?
|
85
|
+
if iterator_pattern != '-'
|
86
|
+
next if iterators.first.nil?
|
87
|
+
iterators = iterators.select{|iter| iter =~ /#{iterator_pattern}/}
|
88
|
+
end
|
89
|
+
if !iterators.empty?
|
90
|
+
if iterators.first.nil?
|
91
|
+
job_names << batch_name
|
92
|
+
else
|
93
|
+
iterators.each do |iter|
|
94
|
+
job_names << batch_name+iter
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return job_names
|
100
|
+
end
|
101
|
+
|
57
102
|
def has_jobs?
|
58
103
|
res = !@jobs.empty?
|
59
104
|
return res
|
60
105
|
end
|
61
106
|
|
107
|
+
def asign_child_batch
|
108
|
+
batches = []
|
109
|
+
if @main_command.class.to_s == 'Array'
|
110
|
+
@main_command.each do |id|
|
111
|
+
batch = get_batch(id)
|
112
|
+
batch.parent = @name
|
113
|
+
batches << batch
|
114
|
+
end
|
115
|
+
@main_command = batches
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_batch(id)
|
120
|
+
selected_batch = nil
|
121
|
+
@@all_batch.each do |name, batch|
|
122
|
+
if batch.id == id
|
123
|
+
selected_batch = batch
|
124
|
+
break
|
125
|
+
end
|
126
|
+
end
|
127
|
+
return selected_batch
|
128
|
+
end
|
129
|
+
|
62
130
|
def get_name_and_iterators_and_modifiers(tag)
|
63
|
-
tag =~ /(^.+)\[([^\]]+)/
|
131
|
+
tag =~ /(^.+)\[([^\]]+)\]\)/ # iterative node
|
64
132
|
name = $1
|
65
|
-
if $1.nil?
|
133
|
+
if $1.nil? # Non iterative node (simple node)
|
66
134
|
tag =~ /(^.+)\)/
|
67
135
|
name = $1
|
68
136
|
end
|
69
137
|
@name , @attrib[:done], @attrib[:folder], @attrib[:buffer] = check_execution_modifiers(name)
|
70
138
|
if !$2.nil?
|
71
139
|
@iterator = []
|
72
|
-
|
140
|
+
#$2.split(';').map{|iter| iter.gsub(')','')}.each do |interval|
|
141
|
+
$2.split(';').each do |interval|
|
73
142
|
if interval.include?('-')
|
74
143
|
limits = interval.split('-')
|
75
144
|
@iterator.concat((limits.first..limits.last).to_a.map{|n| n.to_s})
|
76
145
|
else
|
77
146
|
@iterator << interval
|
78
147
|
end
|
79
|
-
|
148
|
+
end
|
80
149
|
end
|
150
|
+
@@batch_iterator_relations[@name] = @iterator
|
81
151
|
end
|
82
152
|
|
83
|
-
def check_execution_modifiers(name)
|
153
|
+
def check_execution_modifiers(name, iter_type = FALSE) #The last paremeter iused to indicate tha name is a iterator not an orignal node name
|
84
154
|
done = FALSE
|
85
155
|
folder = TRUE
|
86
156
|
buffer = FALSE
|
87
157
|
done = TRUE if name.include?('%')
|
88
158
|
folder = FALSE if name.include?('!')
|
89
159
|
buffer = TRUE if name.include?('&')
|
90
|
-
|
160
|
+
if !iter_type
|
161
|
+
name.gsub!(/&|\!|\%|\)/,'')# Delete function characters
|
162
|
+
else
|
163
|
+
name.gsub!(/&|\!|\%/,'')# Delete function characters
|
164
|
+
end
|
91
165
|
return name, done, folder, buffer
|
92
166
|
end
|
93
167
|
|
@@ -146,32 +220,6 @@ class Batch
|
|
146
220
|
end
|
147
221
|
|
148
222
|
|
149
|
-
def asign_folder(name, local_command = nil)
|
150
|
-
if local_command.nil? #we check a simple job
|
151
|
-
command = @main_command
|
152
|
-
else #we check a cloned job
|
153
|
-
command = local_command
|
154
|
-
end
|
155
|
-
if command.class.to_s == 'Array'
|
156
|
-
folder = nil
|
157
|
-
elsif @attrib[:folder]
|
158
|
-
if @@folder_name == :program_name
|
159
|
-
program = File.join(@attrib[:exec_folder], command.split(' ', 2).first)
|
160
|
-
count = 0
|
161
|
-
folder = program + "_#{"%04d" % count}"
|
162
|
-
while @@all_jobs_relations.values.include?(folder)
|
163
|
-
folder = program + "_#{"%04d" % count}"
|
164
|
-
count += 1
|
165
|
-
end
|
166
|
-
elsif @@folder_name == :job_name
|
167
|
-
folder = File.join(@attrib[:exec_folder], name)
|
168
|
-
end
|
169
|
-
else
|
170
|
-
folder = @attrib[:exec_folder]
|
171
|
-
end
|
172
|
-
return folder
|
173
|
-
end
|
174
|
-
|
175
223
|
def duplicate_job(tmp_j, sufix_name = '')
|
176
224
|
new_job = tmp_j.clone
|
177
225
|
new_job.name = tmp_j.name+'_'+sufix_name
|
@@ -179,7 +227,6 @@ class Batch
|
|
179
227
|
new_job.dependencies = tmp_j.dependencies.clone
|
180
228
|
new_job.initialization = tmp_j.initialization.clone
|
181
229
|
new_job.parameters = tmp_j.parameters.clone
|
182
|
-
new_job.attrib[:exec_folder] = asign_folder(new_job.name, new_job.parameters)
|
183
230
|
return new_job
|
184
231
|
end
|
185
232
|
|
@@ -193,7 +240,7 @@ class Batch
|
|
193
240
|
|
194
241
|
def get_jobs
|
195
242
|
jobs = []
|
196
|
-
|
243
|
+
#@@batch_iterator_relations[@name] = @iterator #??????
|
197
244
|
if @main_command.class.to_s == 'Array' # There are nested batchs
|
198
245
|
temp_jobs = []
|
199
246
|
@main_command.each do |batch|
|
@@ -205,9 +252,9 @@ class Batch
|
|
205
252
|
new_job = duplicate_job(tmp_j, iter)
|
206
253
|
check_dependencies(new_job, iter, temp_jobs)
|
207
254
|
parse_iter(iter, @name, new_job)
|
255
|
+
@@jobs_names << new_job.name
|
208
256
|
jobs << new_job
|
209
257
|
@jobs << new_job
|
210
|
-
@@all_jobs_relations[new_job.name] = new_job.attrib[:exec_folder]
|
211
258
|
jobs2delete << tmp_i
|
212
259
|
end
|
213
260
|
end
|
@@ -216,19 +263,20 @@ class Batch
|
|
216
263
|
@iterator.each_with_index do |iter, num|
|
217
264
|
job_attrib = @attrib.dup
|
218
265
|
if !iter.nil?
|
219
|
-
iter, done, job_attrib[:folder], job_attrib[:buffer] = check_execution_modifiers(iter)
|
266
|
+
iter, done, job_attrib[:folder], job_attrib[:buffer] = check_execution_modifiers(iter, TRUE)
|
220
267
|
job_attrib[:done] = done if !@attrib[:done] # To keep attrib priority in batch on job
|
221
268
|
end
|
222
269
|
name = "#{@name}#{iter}"
|
223
|
-
job_attrib[:exec_folder] = asign_folder(name) if @parent.nil? #Don't asign folder to nested batches (iterative batchs)
|
224
270
|
job_dependencies = []
|
271
|
+
batch_deps = @dependencies.length
|
225
272
|
initialization = replace_dependencies(@initialization, job_dependencies, iter, num)
|
226
273
|
parameters = replace_dependencies(@main_command, job_dependencies, iter, num)
|
274
|
+
@dependencies.pop(@dependencies.length - batch_deps) # Clean temporal dependencies by regexp
|
227
275
|
job = Program.new(name, initialization, parameters, job_dependencies, job_attrib)
|
228
276
|
job.batch = @name
|
277
|
+
@@jobs_names << job.name
|
229
278
|
jobs << job
|
230
279
|
@jobs << job
|
231
|
-
@@all_jobs_relations[name] = job_attrib[:exec_folder]
|
232
280
|
end
|
233
281
|
end
|
234
282
|
return jobs
|
@@ -261,12 +309,101 @@ class Batch
|
|
261
309
|
return string
|
262
310
|
end
|
263
311
|
|
312
|
+
def handle_dependencies(dinamic_variables)
|
313
|
+
[@initialization, @main_command].each do |instructions|
|
314
|
+
if instructions.class.to_s == 'String'
|
315
|
+
scan_dependencies(instructions)
|
316
|
+
dinamic_variables.concat(collect_dinamic_variables(instructions))
|
317
|
+
@dependencies.concat(check_dependencies_with_DinVar(instructions, dinamic_variables))
|
318
|
+
end
|
319
|
+
end
|
320
|
+
return dinamic_variables
|
321
|
+
end
|
322
|
+
|
323
|
+
def scan_dependencies(command)
|
324
|
+
if !command.nil?# When command is the initialize, sometimes can be undefined
|
325
|
+
matched_regions = []
|
326
|
+
batches = []
|
327
|
+
@@all_batch.each do |k , val| #sorting is used to match last jobs first, and avoid small matches of first nodes
|
328
|
+
batches << [k , val]
|
329
|
+
end
|
330
|
+
batches.reverse.each do |name, batch|
|
331
|
+
if command.include?(name+')') && !string_overlap(matched_regions, name+')', command)
|
332
|
+
@dependencies << [name, 'simple', name+')']
|
333
|
+
end
|
334
|
+
if command.include?("!#{name}*!") && !string_overlap(matched_regions, "!#{name}*!", command)
|
335
|
+
@dependencies << [name, '1to1', "!#{name}*!"]
|
336
|
+
end
|
337
|
+
if command.include?("!#{name}!") && !string_overlap(matched_regions, "!#{name}!", command)
|
338
|
+
command =~ /!#{name}!([^ \n]+)/
|
339
|
+
@dependencies << [name, '*to1', "!#{name}!", $1]
|
340
|
+
end
|
341
|
+
local_dependencies = command.scan(/#{name}([^\( \n]+)\)/)
|
342
|
+
local_dependencies.each do |local_dependency|
|
343
|
+
if !string_overlap(matched_regions, "#{name}#{local_dependency.first}"+')', command)
|
344
|
+
@dependencies << [name, 'local', "#{name}#{local_dependency.first}"+')', local_dependency.first]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def get_string_position(substr, string)
|
352
|
+
start = string.index(substr)
|
353
|
+
ending = start + substr.length - 1
|
354
|
+
range = [start, ending]
|
355
|
+
return range
|
356
|
+
end
|
357
|
+
|
358
|
+
def string_overlap(matched_regions, substr, string)
|
359
|
+
match = FALSE
|
360
|
+
range = get_string_position(substr, string)
|
361
|
+
if !range.empty?
|
362
|
+
matched_regions.each do |start, ending|
|
363
|
+
if (range.first >= start && range.first <= ending) ||
|
364
|
+
(range.last >= start && range.last <= ending) ||
|
365
|
+
(range.first <= start && range.last >= ending)
|
366
|
+
match = TRUE
|
367
|
+
break
|
368
|
+
end
|
369
|
+
end
|
370
|
+
matched_regions << range
|
371
|
+
end
|
372
|
+
return match
|
373
|
+
end
|
374
|
+
|
375
|
+
def collect_dinamic_variables(command)
|
376
|
+
dinamic_variables = []
|
377
|
+
if !command.nil? && command.include?('env_manager')
|
378
|
+
command =~ /env_manager "([^"]+)/
|
379
|
+
command =~ /env_manager '([^']+)/ if $1.nil?
|
380
|
+
if !$1.nil?
|
381
|
+
$1.split(';').each do |variable|
|
382
|
+
name, value = variable.split('=')
|
383
|
+
name.gsub!(' ', '') #Remove spaces
|
384
|
+
dinamic_variables << [name, @name]
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
return dinamic_variables
|
389
|
+
end
|
390
|
+
|
391
|
+
def check_dependencies_with_DinVar(command, dinamic_variables)
|
392
|
+
dep = []
|
393
|
+
dinamic_variables.each do |var, name|
|
394
|
+
dep << [name, 'DinVar'] if command.include?(var)
|
395
|
+
end
|
396
|
+
return dep
|
397
|
+
end
|
398
|
+
|
264
399
|
def replace_dependencies(command, job_dependencies, iter, num)
|
265
400
|
if !command.nil?
|
401
|
+
command = command.gsub('(*)', "#{iter}") if command.class.to_s == 'String'
|
402
|
+
scan_dependencies(command)
|
266
403
|
@dependencies.each do |batch_name, dep_type, dep_keyword2replace, dep_info|
|
267
404
|
if dep_type == 'simple'
|
268
405
|
if @@all_batch[batch_name].parent.nil?
|
269
|
-
new_string =
|
406
|
+
new_string = batch_name + ')'
|
270
407
|
end
|
271
408
|
job_dependencies << batch_name
|
272
409
|
elsif dep_type == '1to1'
|
@@ -278,52 +415,39 @@ class Batch
|
|
278
415
|
dep_name = selected_jobs[num].name
|
279
416
|
end
|
280
417
|
job_dependencies << dep_name
|
281
|
-
|
282
|
-
new_string = dep_name + ')'
|
283
|
-
else
|
284
|
-
new_string = @@all_jobs_relations[dep_name]
|
285
|
-
end
|
418
|
+
new_string = dep_name + ')'
|
286
419
|
elsif dep_type == '*to1'
|
287
|
-
if @@all_batch[batch_name].parent.nil?
|
288
|
-
new_string = @@batch_iterator_relations[batch_name].map{|iter|
|
289
|
-
dep_name = batch_name + iter
|
290
|
-
job_dependencies << dep_name
|
291
|
-
"#{@@all_jobs_relations[dep_name]}#{dep_info}"
|
292
|
-
}.join(' ')
|
293
|
-
dep_keyword2replace = "#{dep_keyword2replace}#{dep_info}"
|
294
|
-
elsif !@parent.nil?
|
420
|
+
if @@all_batch[batch_name].parent.nil? || !@parent.nil?
|
295
421
|
new_string = @@batch_iterator_relations[batch_name].map{|iter|
|
296
422
|
dep_name = batch_name + iter
|
297
423
|
job_dependencies << dep_name
|
298
424
|
"#{dep_name})#{dep_info}"
|
299
425
|
}.join(' ')
|
300
|
-
dep_keyword2replace = "#{dep_keyword2replace}#{dep_info}"
|
301
426
|
else
|
302
427
|
root_batch = get_root(batch_name)
|
303
428
|
selected_jobs = root_batch.get_jobs_by_batch_name(batch_name)
|
304
429
|
new_string = selected_jobs.map{|j|
|
305
430
|
job_dependencies << j.name
|
306
|
-
"#{
|
431
|
+
"#{j.name + ')'}#{dep_info}"
|
307
432
|
}.join(' ')
|
308
|
-
dep_keyword2replace = "#{dep_keyword2replace}#{dep_info}"
|
309
433
|
end
|
434
|
+
dep_keyword2replace = "#{dep_keyword2replace}#{dep_info}"
|
310
435
|
elsif dep_type == 'local'
|
311
436
|
if @@all_batch[batch_name].parent.nil? || !@parent.nil?
|
312
|
-
|
437
|
+
#@@batch_iterator_relations.each do |key,val|
|
438
|
+
# puts "#{key}\t#{val.inspect}"
|
439
|
+
#end
|
440
|
+
if @@batch_iterator_relations[batch_name].map{|iter| iter.gsub(')','')}.include?(dep_info) #This avoids cross dependencies by similar names, map used for regexp deps
|
313
441
|
dep_name = batch_name + dep_info
|
314
442
|
job_dependencies << dep_name
|
315
|
-
|
316
|
-
new_string = dep_name + ')'
|
317
|
-
else
|
318
|
-
new_string = @@all_jobs_relations[dep_name]
|
319
|
-
end
|
443
|
+
new_string = dep_name + ')'
|
320
444
|
end
|
321
445
|
else
|
322
446
|
dep_name = dep_keyword2replace.gsub(')','') #This avoids cross dependencies by similar names
|
323
|
-
if !@@
|
324
|
-
|
325
|
-
|
326
|
-
end
|
447
|
+
#if !@@jobs_names.include?(dep_name)
|
448
|
+
job_dependencies << dep_name
|
449
|
+
new_string = dep_name + ')'
|
450
|
+
#end
|
327
451
|
end
|
328
452
|
elsif dep_type == 'DinVar'
|
329
453
|
job_dependencies << batch_name if batch_name != @name # This condition avoids autodependencies
|
@@ -331,9 +455,7 @@ class Batch
|
|
331
455
|
job_dependencies.uniq!
|
332
456
|
command = command.gsub(dep_keyword2replace, new_string) if dep_type != 'DinVar' && !dep_keyword2replace.nil? && !new_string.nil?
|
333
457
|
end
|
334
|
-
|
335
|
-
command = command.gsub('(*)', "#{iter}") if command.class.to_s == 'String'
|
336
|
-
end
|
458
|
+
end
|
337
459
|
return command
|
338
460
|
end
|
339
461
|
|
@@ -139,6 +139,8 @@ class QueueManager
|
|
139
139
|
write_file(sh_name, "source #{File.join(@exec_folder, 'env_file')}") if !@persist_variables.empty?
|
140
140
|
write_job(job, sh_name)
|
141
141
|
write_file(sh_name, "echo -e \"FINISHED #{id} #{job.parameters.split.first}:\\t`date`\" >> #{log_folder}")
|
142
|
+
write_file(sh_name, "echo 'General time'")
|
143
|
+
write_file(sh_name, "times")
|
142
144
|
close_file(sh_name, 0755)
|
143
145
|
|
144
146
|
#Submit node
|
data/lib/autoflow/stack.rb
CHANGED
@@ -18,11 +18,13 @@ class Stack
|
|
18
18
|
:multinode => options[:use_multinode],
|
19
19
|
:ntask => options[:use_ntasks]
|
20
20
|
})
|
21
|
-
|
21
|
+
@@folder_name = :program_name
|
22
|
+
@@folder_name = :job_name if options[:key_name]
|
22
23
|
@commands = {}
|
23
24
|
@variables = {}
|
24
25
|
@persist_variables = {}
|
25
|
-
|
26
|
+
@@all_jobs_relations = {}
|
27
|
+
@exec_folder = exec_folder #TODO move this to queue_manager
|
26
28
|
@do_retry = options[:retry]
|
27
29
|
@options = options
|
28
30
|
parse(options[:workflow], options[:Variables])
|
@@ -85,38 +87,41 @@ class Stack
|
|
85
87
|
# $1 => tag, $2 => initialize, $3 => main command
|
86
88
|
#executions = template_executions.scan(/(^.+\))\s{0,}\{\s{0,}([^\?]{0,})\s{0,}\?\s([^\}]{1,})\s{0,}\}/)
|
87
89
|
#=begin
|
88
|
-
executions = []
|
90
|
+
executions = [] # tag, initialize, main_command
|
89
91
|
states = {} #name => [state, id(position)]
|
90
92
|
#t => tag, i => initialize , c => command
|
91
|
-
|
93
|
+
open_nodes = []
|
92
94
|
template_executions.each_line do |line|
|
93
95
|
line.strip! #Clean al whitespaces at beginning and the end of string
|
94
|
-
node = states[
|
96
|
+
node = states[open_nodes.last] if !open_nodes.empty?
|
95
97
|
if line.empty?
|
96
98
|
next
|
97
|
-
|
99
|
+
# Create nodes and asign nodes states
|
100
|
+
#----------------------------------------
|
101
|
+
elsif line =~ /(\S*\)){$/ #Check tag and create node
|
98
102
|
name = $1
|
99
|
-
executions << [name, '', '']
|
100
|
-
states[name] = [
|
101
|
-
|
103
|
+
executions << [name, '', ''] # create node
|
104
|
+
states[name] = [:i, executions.length - 1]
|
105
|
+
open_nodes << name
|
102
106
|
elsif line == '?' #Check command
|
103
|
-
node[0] =
|
104
|
-
elsif states[names.last].first == 't' || states[names.last].first == 'i' #Check initialize
|
105
|
-
node[0] = 'i' if states[names.last].first == 't'
|
106
|
-
executions[node.last][1] << line +"\n"
|
107
|
+
node[0] = :c
|
107
108
|
elsif line == '}' #Close node
|
108
|
-
finished_node =
|
109
|
-
if !
|
110
|
-
parent_node = states[
|
109
|
+
finished_node = open_nodes.pop
|
110
|
+
if !open_nodes.empty?
|
111
|
+
parent_node = states[open_nodes.last].last #position
|
111
112
|
child_node = states[finished_node].last
|
112
113
|
parent_execution = executions[parent_node]
|
113
|
-
if parent_execution[2].class
|
114
|
+
if parent_execution[2].class == String
|
114
115
|
parent_execution[2] = [child_node]
|
115
116
|
else
|
116
117
|
parent_execution[2] << child_node
|
117
118
|
end
|
118
119
|
end
|
119
|
-
|
120
|
+
# Add lines to nodes
|
121
|
+
#----------------------
|
122
|
+
elsif states[open_nodes.last].first == :i #Add initialize line
|
123
|
+
executions[node.last][1] << line +"\n"
|
124
|
+
elsif states[open_nodes.last].first == :c #Add command line
|
120
125
|
executions[node.last][2] << line +"\n"
|
121
126
|
end
|
122
127
|
end
|
@@ -136,29 +141,19 @@ class Stack
|
|
136
141
|
nodes = create_ids(nodes)
|
137
142
|
|
138
143
|
#nodes.each do |tag, init, command, index|
|
139
|
-
# puts "#{tag.colorize(:red)}\t#{index}\n#{init.chomp.colorize(:blue)}\n#{command.to_s.colorize(:green)}"
|
144
|
+
# puts "#{tag.colorize(:red)}\t#{index}\n#{('-'*tag.length).colorize(:red)}\n#{init.chomp.colorize(:blue)}\n#{command.to_s.colorize(:green)}"
|
140
145
|
#end
|
141
146
|
|
142
|
-
nodes.each do |tag, init, command, index| #Takes the info of each node of workflow
|
143
|
-
# Set batch
|
144
|
-
new_batch = Batch.new(tag, init, command, index, @exec_folder)
|
145
|
-
|
146
|
-
#Dependencies
|
147
|
-
#-- Check initialize
|
148
|
-
scan_dependencies(init, new_batch.dependencies, dinamic_variables)
|
149
|
-
dinamic_variables.concat(collect_dinamic_variables(init, tag))
|
150
|
-
#-- Check command
|
151
|
-
if command.class.to_s == 'String'
|
152
|
-
scan_dependencies(command, new_batch.dependencies, dinamic_variables)
|
153
|
-
dinamic_variables.concat(collect_dinamic_variables(command, tag))
|
154
|
-
end
|
155
|
-
|
147
|
+
nodes.each do |tag, init, command, index| #Takes the info of each node of workflow to create the job
|
148
|
+
# Set batch
|
149
|
+
new_batch = Batch.new(tag, init, command, index, @exec_folder)
|
150
|
+
dinamic_variables.concat(new_batch.handle_dependencies(dinamic_variables))
|
156
151
|
@commands[new_batch.name] = new_batch
|
157
152
|
end
|
158
153
|
|
159
154
|
# link each parent batch to a child batch
|
160
155
|
@commands.each do |name, batch|
|
161
|
-
batch.
|
156
|
+
batch.asign_child_batch
|
162
157
|
end
|
163
158
|
end
|
164
159
|
|
@@ -169,95 +164,54 @@ class Stack
|
|
169
164
|
return nodes
|
170
165
|
end
|
171
166
|
|
172
|
-
def
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
batches << batch
|
178
|
-
end
|
179
|
-
return batches
|
180
|
-
end
|
181
|
-
|
182
|
-
def get_batch(id)
|
183
|
-
selected_batch = nil
|
184
|
-
@commands.each do |name, batch|
|
185
|
-
if batch.id == id
|
186
|
-
selected_batch = batch
|
187
|
-
break
|
188
|
-
end
|
167
|
+
def set_dependencies_path(job) #TODO move this to queue_manager
|
168
|
+
job.dependencies.sort{|d1, d2| d2.length <=> d1.length}.each do |dep|
|
169
|
+
path = @@all_jobs_relations[dep]
|
170
|
+
job.initialization.gsub!(dep+')', path) if !job.initialization.nil?
|
171
|
+
job.parameters.gsub!(dep+')', path)
|
189
172
|
end
|
190
|
-
return selected_batch
|
191
173
|
end
|
192
174
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
dinamic_variables << [name, node_name.gsub(')','')]
|
175
|
+
def asign_folder(job) #TODO move this to queue_manager
|
176
|
+
folder = nil
|
177
|
+
if job.attrib[:folder]
|
178
|
+
if @@folder_name == :program_name
|
179
|
+
program = File.join(job.attrib[:exec_folder], job.parameters.split(' ', 2).first)
|
180
|
+
count = 0
|
181
|
+
folder = program + "_#{"%04d" % count}"
|
182
|
+
while @@all_jobs_relations.values.include?(folder)
|
183
|
+
folder = program + "_#{"%04d" % count}"
|
184
|
+
count += 1
|
204
185
|
end
|
186
|
+
elsif @@folder_name == :job_name
|
187
|
+
folder = File.join(job.attrib[:exec_folder], job.name)
|
205
188
|
end
|
189
|
+
else
|
190
|
+
folder = job.attrib[:exec_folder]
|
206
191
|
end
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
def scan_dependencies(command, dependencies, dinamic_variables)
|
211
|
-
if !command.nil?# When command is the initialize, sometimes can be undefined
|
212
|
-
@commands.each do |name, batch|
|
213
|
-
if command.include?(name+')')
|
214
|
-
dependencies << [name, 'simple', name+')']
|
215
|
-
end
|
216
|
-
if command.include?("!#{name}*!")
|
217
|
-
dependencies << [name, '1to1', "!#{name}*!"]
|
218
|
-
end
|
219
|
-
if command.include?("!#{name}!")
|
220
|
-
command =~ /!#{name}!([^ \n]+)/
|
221
|
-
dependencies << [name, '*to1', "!#{name}!", $1]
|
222
|
-
end
|
223
|
-
local_dependencies = command.scan(/#{name}([^\( \n]+)\)/)
|
224
|
-
local_dependencies.each do |local_dependency|
|
225
|
-
dependencies << [name, 'local', "#{name}#{local_dependency.first}"+')', local_dependency.first]
|
226
|
-
end
|
227
|
-
end
|
228
|
-
dependencies.concat(check_dependencies_with_DinVar(command, dinamic_variables))
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def check_dependencies_with_DinVar(command, dinamic_variables)
|
233
|
-
dep = []
|
234
|
-
dinamic_variables.each do |var, name|
|
235
|
-
dep << [name, 'DinVar'] if command.include?(var)
|
236
|
-
end
|
237
|
-
return dep
|
238
|
-
end
|
239
|
-
|
240
|
-
def set_path(job, all_jobs_relations)
|
241
|
-
job.dependencies.each do |dep|
|
242
|
-
path = all_jobs_relations[dep]
|
243
|
-
job.initialization.gsub!(dep+')', path)
|
244
|
-
job.parameters.gsub!(dep+')', path)
|
245
|
-
end
|
192
|
+
@@all_jobs_relations[job.name.gsub(')','')] = folder
|
193
|
+
job.attrib[:exec_folder] = folder
|
246
194
|
end
|
247
|
-
|
195
|
+
|
248
196
|
def get_jobs
|
249
197
|
jobs =[]
|
250
|
-
all_jobs_relations = Batch.get_job_relations
|
251
198
|
@commands.each do |name, batch|
|
252
|
-
next if batch.has_jobs?
|
199
|
+
next if batch.has_jobs? #parent batch (intermediates)
|
253
200
|
batch.get_jobs.each do |j|
|
254
|
-
|
201
|
+
folder = asign_folder(j) #TODO move this to queue_manager
|
255
202
|
jobs << [j.name, j]
|
256
203
|
end
|
204
|
+
|
205
|
+
end
|
206
|
+
jobs.each do |j_name, job| #TODO move this to queue_manager
|
207
|
+
set_dependencies_path(job)
|
208
|
+
j_name.gsub!(')','') #Clean function characters on name
|
209
|
+
job.name.gsub!(')','')
|
257
210
|
end
|
258
211
|
return jobs
|
259
212
|
end
|
260
213
|
|
214
|
+
|
261
215
|
def get_jobs_relations
|
262
216
|
hash = {}
|
263
217
|
get_jobs.each do |name, job|
|
@@ -266,6 +220,11 @@ class Stack
|
|
266
220
|
return hash
|
267
221
|
end
|
268
222
|
|
223
|
+
def comment_main_command
|
224
|
+
@jobs.each do |name, job|
|
225
|
+
job.parameters = "##{job.parameters}"
|
226
|
+
end
|
227
|
+
end
|
269
228
|
##########################################################################################
|
270
229
|
## WORKFLOW REPRESENTATION
|
271
230
|
##########################################################################################
|
data/lib/autoflow/version.rb
CHANGED
data/test/stack_test.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class StackTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_scan_nodes
|
10
|
+
stack = Stack.new('exec', {
|
11
|
+
:cpu => 16,
|
12
|
+
:mem => '4gb',
|
13
|
+
:time => '20:00:00',
|
14
|
+
:node => nil,
|
15
|
+
:multinode => 0,
|
16
|
+
:ntask => FALSE,
|
17
|
+
:key_name => FALSE,
|
18
|
+
:retry => FALSE,
|
19
|
+
:Variables => nil,
|
20
|
+
:workflow => ''
|
21
|
+
})
|
22
|
+
|
23
|
+
# test simple node
|
24
|
+
#---------------------------------------------
|
25
|
+
|
26
|
+
node_lines = [
|
27
|
+
#Single node
|
28
|
+
"result){\n",
|
29
|
+
"null\n",
|
30
|
+
"?\n",
|
31
|
+
"touch algo\n",
|
32
|
+
"}\n"
|
33
|
+
]
|
34
|
+
|
35
|
+
result = [
|
36
|
+
["result)", "null\n", "touch algo\n"]
|
37
|
+
]
|
38
|
+
|
39
|
+
test = stack.scan_nodes(node_lines)
|
40
|
+
assert_equal(result, test)
|
41
|
+
|
42
|
+
# test double node
|
43
|
+
#---------------------------------------------
|
44
|
+
|
45
|
+
node_lines = [
|
46
|
+
#Single node
|
47
|
+
"algo){\n",
|
48
|
+
"null\n",
|
49
|
+
"?\n",
|
50
|
+
"echo 'OK'\n",
|
51
|
+
"}\n",
|
52
|
+
#Single node
|
53
|
+
"result){\n",
|
54
|
+
"null\n",
|
55
|
+
"?\n",
|
56
|
+
"touch algo\n",
|
57
|
+
"}\n"
|
58
|
+
]
|
59
|
+
|
60
|
+
result = [
|
61
|
+
["algo)", "null\n", "echo 'OK'\n"],
|
62
|
+
["result)", "null\n", "touch algo\n"]
|
63
|
+
]
|
64
|
+
|
65
|
+
test = stack.scan_nodes(node_lines)
|
66
|
+
assert_equal(result, test)
|
67
|
+
|
68
|
+
# test nested iterative node
|
69
|
+
#---------------------------------------------
|
70
|
+
|
71
|
+
node_lines = [
|
72
|
+
#Nested iterative nodes
|
73
|
+
"itera_[11;22]){\n",
|
74
|
+
"algo_[aa;bb]){\n",
|
75
|
+
"null\n",
|
76
|
+
"?\n",
|
77
|
+
"echo 'OK'itera_(+)(*)\n",
|
78
|
+
"}\n",
|
79
|
+
"}\n"
|
80
|
+
]
|
81
|
+
|
82
|
+
result = [
|
83
|
+
["itera_[11;22])", "", [1]],
|
84
|
+
["algo_[aa;bb])", "null\n", "echo 'OK'itera_(+)(*)\n"]
|
85
|
+
]
|
86
|
+
|
87
|
+
test = stack.scan_nodes(node_lines)
|
88
|
+
assert_equal(result, test)
|
89
|
+
|
90
|
+
# test nested 3 iterative node
|
91
|
+
#---------------------------------------------
|
92
|
+
|
93
|
+
node_lines = [
|
94
|
+
#Nested iterative nodes
|
95
|
+
"itera_[11;22]){\n",
|
96
|
+
"?\n",
|
97
|
+
"algo_[aa;bb]){\n",
|
98
|
+
"?\n",
|
99
|
+
"mas_[ZZ;YY]){\n",
|
100
|
+
"null\n",
|
101
|
+
"?\n",
|
102
|
+
"echo 'OK'itera_(+)(*)mas_(+)\n",
|
103
|
+
"}\n",
|
104
|
+
"}\n",
|
105
|
+
"}\n"
|
106
|
+
]
|
107
|
+
|
108
|
+
result = [
|
109
|
+
["itera_[11;22])", "", [1]],
|
110
|
+
["algo_[aa;bb])", "", [2]],
|
111
|
+
["mas_[ZZ;YY])", "null\n", "echo 'OK'itera_(+)(*)mas_(+)\n"]
|
112
|
+
]
|
113
|
+
|
114
|
+
test = stack.scan_nodes(node_lines)
|
115
|
+
assert_equal(result, test)
|
116
|
+
|
117
|
+
#test nested iterative nodes with a final simgle node (bug)
|
118
|
+
#--------------------------------------------------------------
|
119
|
+
|
120
|
+
node_lines = [
|
121
|
+
#Nested iterative nodes
|
122
|
+
"itera_[11;22]){\n",
|
123
|
+
"algo_[aa;bb]){\n",
|
124
|
+
"null\n",
|
125
|
+
"?\n",
|
126
|
+
"echo 'OK'itera_(+)(*)\n",
|
127
|
+
"}\n",
|
128
|
+
"}\n",
|
129
|
+
#Single node
|
130
|
+
"result){\n",
|
131
|
+
"null\n",
|
132
|
+
"?\n",
|
133
|
+
"touch algo\n",
|
134
|
+
"}\n"
|
135
|
+
]
|
136
|
+
|
137
|
+
result = [
|
138
|
+
["itera_[11;22])", "", [1]],
|
139
|
+
["algo_[aa;bb])", "null\n", "echo 'OK'itera_(+)(*)\n"],
|
140
|
+
["result)", "null\n", "touch algo\n"]
|
141
|
+
]
|
142
|
+
|
143
|
+
test = stack.scan_nodes(node_lines)
|
144
|
+
assert_equal(result, test)
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
ROOT_PATH=File.dirname(__FILE__)
|
5
|
+
$: << File.expand_path(File.join(ROOT_PATH, "../lib/"))
|
6
|
+
$: << File.expand_path(File.join(ROOT_PATH, "../lib/autoflow/"))
|
7
|
+
$: << File.expand_path(File.join(ROOT_PATH, "../lib/autoflow/queue_managers"))
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/../lib/autoflow/program'
|
10
|
+
require File.dirname(__FILE__) + '/../lib/autoflow/batch'
|
11
|
+
require File.dirname(__FILE__) + '/../lib/autoflow/stack'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autoflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-03-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -103,6 +103,8 @@ files:
|
|
103
103
|
- lib/autoflow/queue_managers/slurm_manager.rb
|
104
104
|
- lib/autoflow/stack.rb
|
105
105
|
- lib/autoflow/version.rb
|
106
|
+
- test/stack_test.rb
|
107
|
+
- test/test_helper.rb
|
106
108
|
homepage: ''
|
107
109
|
licenses:
|
108
110
|
- MIT
|
@@ -128,4 +130,6 @@ rubygems_version: 1.8.23
|
|
128
130
|
signing_key:
|
129
131
|
specification_version: 3
|
130
132
|
summary: ! '"This gem take a pipeline and launch it on a queue system"'
|
131
|
-
test_files:
|
133
|
+
test_files:
|
134
|
+
- test/stack_test.rb
|
135
|
+
- test/test_helper.rb
|