tap 0.7.9
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/MIT-LICENSE +21 -0
- data/README +71 -0
- data/Rakefile +117 -0
- data/bin/tap +63 -0
- data/lib/tap.rb +15 -0
- data/lib/tap/app.rb +739 -0
- data/lib/tap/file_task.rb +354 -0
- data/lib/tap/generator.rb +29 -0
- data/lib/tap/generator/generators/config/USAGE +0 -0
- data/lib/tap/generator/generators/config/config_generator.rb +23 -0
- data/lib/tap/generator/generators/config/templates/config.erb +2 -0
- data/lib/tap/generator/generators/file_task/USAGE +0 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
- data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
- data/lib/tap/generator/generators/root/USAGE +0 -0
- data/lib/tap/generator/generators/root/root_generator.rb +36 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
- data/lib/tap/generator/generators/root/templates/app.yml +19 -0
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
- data/lib/tap/generator/generators/task/USAGE +0 -0
- data/lib/tap/generator/generators/task/task_generator.rb +21 -0
- data/lib/tap/generator/generators/task/templates/task.erb +21 -0
- data/lib/tap/generator/generators/task/templates/test.erb +29 -0
- data/lib/tap/generator/generators/workflow/USAGE +0 -0
- data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
- data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
- data/lib/tap/generator/options.rb +26 -0
- data/lib/tap/generator/usage.rb +26 -0
- data/lib/tap/root.rb +275 -0
- data/lib/tap/script/console.rb +7 -0
- data/lib/tap/script/destroy.rb +8 -0
- data/lib/tap/script/generate.rb +8 -0
- data/lib/tap/script/run.rb +111 -0
- data/lib/tap/script/server.rb +12 -0
- data/lib/tap/support/audit.rb +415 -0
- data/lib/tap/support/batch_queue.rb +165 -0
- data/lib/tap/support/combinator.rb +114 -0
- data/lib/tap/support/logger.rb +91 -0
- data/lib/tap/support/rap.rb +38 -0
- data/lib/tap/support/run_error.rb +20 -0
- data/lib/tap/support/template.rb +81 -0
- data/lib/tap/support/templater.rb +155 -0
- data/lib/tap/support/versions.rb +63 -0
- data/lib/tap/task.rb +448 -0
- data/lib/tap/test.rb +320 -0
- data/lib/tap/test/env_vars.rb +16 -0
- data/lib/tap/test/inference_methods.rb +298 -0
- data/lib/tap/test/subset_methods.rb +260 -0
- data/lib/tap/version.rb +3 -0
- data/lib/tap/workflow.rb +73 -0
- data/test/app/config/addition_template.yml +6 -0
- data/test/app/config/batch.yml +2 -0
- data/test/app/config/empty.yml +0 -0
- data/test/app/config/erb.yml +1 -0
- data/test/app/config/template.yml +6 -0
- data/test/app/config/version-0.1.yml +1 -0
- data/test/app/config/version.yml +1 -0
- data/test/app/lib/app_test_task.rb +2 -0
- data/test/app_class_test.rb +33 -0
- data/test/app_test.rb +1372 -0
- data/test/file_task/config/batch.yml +2 -0
- data/test/file_task/config/configured.yml +1 -0
- data/test/file_task/old_file_one.txt +0 -0
- data/test/file_task/old_file_two.txt +0 -0
- data/test/file_task_test.rb +1041 -0
- data/test/root/alt_lib/alt_module.rb +4 -0
- data/test/root/lib/absolute_alt_filepath.rb +2 -0
- data/test/root/lib/alternative_filepath.rb +2 -0
- data/test/root/lib/another_module.rb +2 -0
- data/test/root/lib/nested/some_module.rb +4 -0
- data/test/root/lib/no_module_included.rb +0 -0
- data/test/root/lib/some/module.rb +4 -0
- data/test/root/lib/some_class.rb +2 -0
- data/test/root/lib/some_module.rb +3 -0
- data/test/root/load_path/load_path_module.rb +2 -0
- data/test/root/load_path/skip_module.rb +2 -0
- data/test/root/mtime/older.txt +0 -0
- data/test/root/unload/full_path.rb +2 -0
- data/test/root/unload/loaded_by_nested.rb +2 -0
- data/test/root/unload/nested/nested_load.rb +6 -0
- data/test/root/unload/nested/nested_with_ext.rb +4 -0
- data/test/root/unload/nested/relative_path.rb +4 -0
- data/test/root/unload/older.rb +2 -0
- data/test/root/unload/unload_base.rb +9 -0
- data/test/root/versions/another.yml +0 -0
- data/test/root/versions/file-0.1.2.yml +0 -0
- data/test/root/versions/file-0.1.yml +0 -0
- data/test/root/versions/file.yml +0 -0
- data/test/root_test.rb +483 -0
- data/test/support/audit_test.rb +449 -0
- data/test/support/batch_queue_test.rb +320 -0
- data/test/support/combinator_test.rb +249 -0
- data/test/support/logger_test.rb +31 -0
- data/test/support/template_test.rb +122 -0
- data/test/support/templater/erb.txt +2 -0
- data/test/support/templater/erb.yml +2 -0
- data/test/support/templater/somefile.txt +2 -0
- data/test/support/templater_test.rb +192 -0
- data/test/support/versions_test.rb +71 -0
- data/test/tap_test_helper.rb +4 -0
- data/test/tap_test_suite.rb +4 -0
- data/test/task/config/batch.yml +2 -0
- data/test/task/config/batched.yml +2 -0
- data/test/task/config/configured.yml +1 -0
- data/test/task/config/example.yml +1 -0
- data/test/task/config/overriding.yml +2 -0
- data/test/task/config/task_with_config.yml +1 -0
- data/test/task/config/template.yml +4 -0
- data/test/task_class_test.rb +118 -0
- data/test/task_execute_test.rb +233 -0
- data/test/task_test.rb +424 -0
- data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
- data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
- data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
- data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
- data/test/test/inference_methods_test.rb +311 -0
- data/test/test/subset_methods_test.rb +115 -0
- data/test/test_test.rb +233 -0
- data/test/workflow_test.rb +108 -0
- metadata +274 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'benchmark'
|
|
3
|
+
require 'pp'
|
|
4
|
+
require 'tap/test/env_vars'
|
|
5
|
+
|
|
6
|
+
module Test # :nodoc:
|
|
7
|
+
module Unit # :nodoc:
|
|
8
|
+
class TestCase
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
# Causes a whole test suite to require one of the listed platforms
|
|
12
|
+
# in order to run. See match_platform? for details.
|
|
13
|
+
#
|
|
14
|
+
# Simply declare like:
|
|
15
|
+
#
|
|
16
|
+
# class Test::Unit::TestCase
|
|
17
|
+
# require_platform 'mswin'
|
|
18
|
+
# end
|
|
19
|
+
def require_platform(*platforms, &block)
|
|
20
|
+
@platforms = platforms
|
|
21
|
+
yield if block_given? && match_platform?(*@platforms)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns true if RUBY_PLATFORM matches one of the specfied
|
|
25
|
+
# platforms. Use the prefix 'non_' to specify any plaform but the
|
|
26
|
+
# specified platform (ex: 'non_mswin')
|
|
27
|
+
#
|
|
28
|
+
# Some common platforms:
|
|
29
|
+
# - mswin:: Windows
|
|
30
|
+
# - darwin:: Mac
|
|
31
|
+
def match_platform?(*platforms)
|
|
32
|
+
platforms.each do |platform|
|
|
33
|
+
platform.to_s =~ /^(non_)?(.*)/
|
|
34
|
+
|
|
35
|
+
non = true if $1
|
|
36
|
+
match_platform = !RUBY_PLATFORM.index($2).nil?
|
|
37
|
+
return false unless (non && !match_platform) || (!non && match_platform)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
alias :original_suite :suite
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module Tap
|
|
50
|
+
module Test
|
|
51
|
+
module SubsetMethods
|
|
52
|
+
include Tap::Test::EnvVars
|
|
53
|
+
|
|
54
|
+
def self.suite
|
|
55
|
+
if match_platform?(*@platforms)
|
|
56
|
+
original_suite
|
|
57
|
+
else
|
|
58
|
+
# if platforms are specfied for the tests and the platform does NOT
|
|
59
|
+
# match, remake the suite to skip all tests.
|
|
60
|
+
method_names = public_instance_methods(true)
|
|
61
|
+
suite = Test::Unit::TestSuite.new(name)
|
|
62
|
+
method_names.each do |method_name|
|
|
63
|
+
catch(:invalid_test) do
|
|
64
|
+
suite << new('on_test_skipped') if method_name =~ /^test/
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
suite
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Platform-specific test. Useful for specifying test that should only be run on,
|
|
72
|
+
# for instance, windows. See match_platform? for details.
|
|
73
|
+
def platform_test(*platforms, &block)
|
|
74
|
+
if self.class.match_platform?(*platforms)
|
|
75
|
+
yield
|
|
76
|
+
else
|
|
77
|
+
print ' '
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Subset test declaration for extended tests -- type: EXTENDED
|
|
82
|
+
# Prints 'x' unless run.
|
|
83
|
+
def extended_test(&block)
|
|
84
|
+
subset_test("EXTENDED", "x", &block)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Subset test declaration for benchmark tests -- type: BENCHMARK
|
|
88
|
+
# Prints 'b' unless run.
|
|
89
|
+
def benchmark_test(length=10, &block)
|
|
90
|
+
subset_test("BENCHMARK") do
|
|
91
|
+
puts
|
|
92
|
+
puts calling_method
|
|
93
|
+
bm(length) do |x|
|
|
94
|
+
yield(x)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Case tests take a hash of testcases and expected values. Each pair in the hash will
|
|
100
|
+
# be passed to the block if type CASE_TEST is specified. Individual cases tests can be
|
|
101
|
+
# specified by providing a regexp in CASE; the testcase will run if the pretty-print of
|
|
102
|
+
# the testcase matches the provided regexp. Example:
|
|
103
|
+
#
|
|
104
|
+
# case_test(
|
|
105
|
+
# [1,2,3] => 'an array testcase',
|
|
106
|
+
# '[1, 2, 3]' => 'the pretty print of the array',
|
|
107
|
+
# 'another testcase' => 'some third testcase'
|
|
108
|
+
# ).do |testcase, expected|
|
|
109
|
+
# ...your test code...
|
|
110
|
+
# end
|
|
111
|
+
#
|
|
112
|
+
# ENV['CASE_TEST']=true => all tests run
|
|
113
|
+
# ENV['CASE']='1, 2, 3' => first two tests run
|
|
114
|
+
# ENV['CASE']='another' => only last test runs
|
|
115
|
+
def case_test(hash, &block)
|
|
116
|
+
if match_regexp?("CASE_TEST", calling_method)
|
|
117
|
+
hash.each_pair do |testcase, expected|
|
|
118
|
+
yield(testcase, expected) if match_regexp?("CASE", testcase)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Acase tests take an array of testcases. Each testcase in the array will
|
|
124
|
+
# be passed to the block if type CASE_TEST is specified. Individual cases tests can be
|
|
125
|
+
# specified by providing a regexp in CASE; the testcase will run if the pretty-print of
|
|
126
|
+
# the testcase matches the provided regexp. Example:
|
|
127
|
+
#
|
|
128
|
+
# case_test(
|
|
129
|
+
# [1,2,3] ,
|
|
130
|
+
# '[1, 2, 3]',
|
|
131
|
+
# 'another testcase'
|
|
132
|
+
# ).do |testcase|
|
|
133
|
+
# ...your test code...
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
136
|
+
# ENV['CASE_TEST']=true => all tests run
|
|
137
|
+
# ENV['CASE']='1, 2, 3' => first two tests run
|
|
138
|
+
# ENV['CASE']='another' => only last test runs
|
|
139
|
+
def acase_test(*array)
|
|
140
|
+
if match_regexp?("CASE_TEST", calling_method)
|
|
141
|
+
array.each do |testcase|
|
|
142
|
+
yield(testcase) if match_regexp?("CASE", testcase)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Subset test declaration for prompt tests -- type: PROMPT
|
|
149
|
+
# Prints 'p' unless run.
|
|
150
|
+
#
|
|
151
|
+
# Useful for tests that require user input. If run, then this test will
|
|
152
|
+
# prompt the user for an input for each item in the array. The results
|
|
153
|
+
# are collected in a hash and passed to the block.
|
|
154
|
+
#
|
|
155
|
+
# Example:
|
|
156
|
+
#
|
|
157
|
+
# def test_something_important
|
|
158
|
+
# prompt_test(:a, :b, :c).do |config|
|
|
159
|
+
# ...your test code...
|
|
160
|
+
# end
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# (if run, prompts print to $stdout the following:)
|
|
164
|
+
# test_something_important: Enter values or 'skip'
|
|
165
|
+
# a: # => enter 'avalue'
|
|
166
|
+
# b: # => enter 'bvalue'
|
|
167
|
+
# c: # => enter 'cvalue'
|
|
168
|
+
#
|
|
169
|
+
# # config => {:a => 'avalue', :b => 'bvalue', :c => 'cvalue'}
|
|
170
|
+
def prompt_test(*array, &block)
|
|
171
|
+
subset_test("PROMPT", "p") do
|
|
172
|
+
puts "\n#{calling_method} -- Enter values or 'skip'."
|
|
173
|
+
|
|
174
|
+
config = {}
|
|
175
|
+
array.each do |key|
|
|
176
|
+
print "#{key}: "
|
|
177
|
+
value = gets.strip
|
|
178
|
+
flunk "skipped test" if value =~ /skip/i
|
|
179
|
+
|
|
180
|
+
config[key] = value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
yield(config)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
protected
|
|
188
|
+
|
|
189
|
+
# Formats the input by using singleline_pp
|
|
190
|
+
def spp(input, str='')
|
|
191
|
+
PP.singleline_pp(input, str)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Required for platform tests
|
|
195
|
+
def on_test_skipped
|
|
196
|
+
print ' '
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns true if the env_var(var) is set and matches /^true%/i
|
|
200
|
+
def env_true?(var)
|
|
201
|
+
env(var) && env(var) =~ /^true$/i
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Returns true if the subset type or 'ALL' is specified in ENV
|
|
205
|
+
def run_subset?(type)
|
|
206
|
+
env_true?(type) || env_true?("ALL") ? true : false
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Returns true if the pretty-print string for obj matches the regexp specified in env_var(type).
|
|
210
|
+
# Returns the default value if 'ALL' is specified in ENV or type is not specified in ENV.
|
|
211
|
+
def match_regexp?(type, obj, default=true)
|
|
212
|
+
return true if env_true?("ALL")
|
|
213
|
+
return default unless env(type)
|
|
214
|
+
|
|
215
|
+
spp(obj) =~ Regexp.new(env(type)) ? true : false
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Calling method iterates over the call stack, and returns the first calling
|
|
219
|
+
# method name that matches the input pattern (by default /^test/)
|
|
220
|
+
def calling_method(pattern=/^test/)
|
|
221
|
+
0.upto(caller.length) do |i|
|
|
222
|
+
caller[i] =~ /:in `(.*)'$/
|
|
223
|
+
method_name = $1
|
|
224
|
+
return method_name if method_name =~ pattern
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
''
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Basic method for a subset test. The provided block will run if:
|
|
231
|
+
# - The subset type or 'ALL' is specified in ENV
|
|
232
|
+
# - The calling method matches the regexp provided in the "TYPE_TEST" ENV variable
|
|
233
|
+
#
|
|
234
|
+
# Otherwise the block will be skipped and +skip+ will be printed. By default skip
|
|
235
|
+
# is the first letter of +type+.
|
|
236
|
+
def subset_test(type, skip=type[0..0].downcase, &block)
|
|
237
|
+
type = type.upcase
|
|
238
|
+
type_test = "#{type}_TEST"
|
|
239
|
+
if run_subset?(type) || env(type_test)
|
|
240
|
+
if match_regexp?(type_test, calling_method)
|
|
241
|
+
yield
|
|
242
|
+
else
|
|
243
|
+
print skip
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
print skip
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Runs only if the first argument causes an 'if' statement to return true.
|
|
251
|
+
def switch_test(run_test, skip="!", &block)
|
|
252
|
+
if run_test
|
|
253
|
+
yield
|
|
254
|
+
else
|
|
255
|
+
print skip
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
data/lib/tap/version.rb
ADDED
data/lib/tap/workflow.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Tap
|
|
2
|
+
|
|
3
|
+
# App can build workflows directly, using methods like sequence, merge, and
|
|
4
|
+
# multithread, but these workflows are hard to abstract and resuse. Workflow
|
|
5
|
+
# is a specialized type of Task allows the encapsulation and reuse of workflow
|
|
6
|
+
# logic.
|
|
7
|
+
#
|
|
8
|
+
# During initialization, Workflows execute the workflow method to define the
|
|
9
|
+
# workflow logic. The workflow method defines an entry_point task, which is
|
|
10
|
+
# sequenced to execute after the Workflow instance completes. Workflows by
|
|
11
|
+
# default just pass their inputs along, essentially acting as a gateway for
|
|
12
|
+
# executing entry_point:
|
|
13
|
+
#
|
|
14
|
+
# inputs to workflow
|
|
15
|
+
# workflow executes (does nothing)
|
|
16
|
+
# on_complete entry_point recieves inputs
|
|
17
|
+
# entry_point executes, beginning workflow logic
|
|
18
|
+
#
|
|
19
|
+
# Since entry_point and all other tasks in the workflow logic are defined
|
|
20
|
+
# in the workflow method, they can be made instance-specific. Thus you can
|
|
21
|
+
# instantiate a Workflow many times and use them at many points in a larger
|
|
22
|
+
# workflow, or in a multithreaded way.
|
|
23
|
+
#
|
|
24
|
+
# Notes:
|
|
25
|
+
# - The default Workflow uses the task blocks to define workflow logic, NOT
|
|
26
|
+
# process logic as in Task.
|
|
27
|
+
class Workflow < Task
|
|
28
|
+
attr_accessor :entry_point, :exit_point
|
|
29
|
+
|
|
30
|
+
def initialize(*args)
|
|
31
|
+
super
|
|
32
|
+
|
|
33
|
+
self.entry_point = {}
|
|
34
|
+
self.exit_point = {}
|
|
35
|
+
self.workflow
|
|
36
|
+
|
|
37
|
+
case entry_point
|
|
38
|
+
when Hash, Array
|
|
39
|
+
raise WorkflowError.new("No entry points defined") if entry_point.empty?
|
|
40
|
+
targets = entry_point.kind_of?(Hash) ? entry_point.values : entry_point
|
|
41
|
+
fork(self, *targets)
|
|
42
|
+
when Task
|
|
43
|
+
sequence(self, entry_point)
|
|
44
|
+
else
|
|
45
|
+
raise WorkflowError.new("No entry point defined")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def sequence(*tasks)
|
|
50
|
+
app.sequence(*tasks)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fork(source, *targets)
|
|
54
|
+
app.fork(source, *targets)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def merge(target, *sources)
|
|
58
|
+
app.merge(target, *sources)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def workflow
|
|
62
|
+
raise WorkflowError.new("No workflow definition provided.") unless task_block
|
|
63
|
+
task_block.call(self)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def process(input)
|
|
67
|
+
input
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class WorkflowError < Exception # :nodoc:
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sum: <%= 1 + 2 %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version: 0.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version: empty
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'tap_test_helper')
|
|
2
|
+
require 'tap/app'
|
|
3
|
+
|
|
4
|
+
class AppClassTest < Test::Unit::TestCase
|
|
5
|
+
include Tap
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# instance tests
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
def test_instance_returns_current_instance_or_a_default_app
|
|
12
|
+
a = App.new
|
|
13
|
+
App.instance = a
|
|
14
|
+
assert_equal a, App.instance
|
|
15
|
+
|
|
16
|
+
App.instance = nil
|
|
17
|
+
assert_equal App, App.instance.class
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# parse_yaml tests
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
def test_parse_yaml_loads_arg_if_arg_matches_yaml_document_string
|
|
25
|
+
string = "---\nkey: value"
|
|
26
|
+
assert_equal({"key" => "value"}, App.parse_yaml(string))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_parse_yaml_returns_arg_unless_matches_yaml_document_string
|
|
30
|
+
string = "key: value"
|
|
31
|
+
assert_equal("key: value", App.parse_yaml(string))
|
|
32
|
+
end
|
|
33
|
+
end
|
data/test/app_test.rb
ADDED
|
@@ -0,0 +1,1372 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'tap_test_helper')
|
|
2
|
+
require 'tap/app'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'logger'
|
|
5
|
+
|
|
6
|
+
class AppTest < Test::Unit::TestCase
|
|
7
|
+
include Tap
|
|
8
|
+
acts_as_tap_test
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
super
|
|
12
|
+
app.queue.clear
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# initialization tests
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
def test_default_app
|
|
20
|
+
app = App.new
|
|
21
|
+
|
|
22
|
+
assert_equal Dir.pwd, app.root
|
|
23
|
+
assert_equal({}, app.directories)
|
|
24
|
+
assert_equal({}, app.options.marshal_dump)
|
|
25
|
+
assert_equal({}, app.map)
|
|
26
|
+
assert_equal App::State::READY, app.state
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# task config tests
|
|
31
|
+
#
|
|
32
|
+
|
|
33
|
+
def test_config_returns_current_configurations
|
|
34
|
+
app = App.new
|
|
35
|
+
expected = {
|
|
36
|
+
:root => Dir.pwd,
|
|
37
|
+
:directories => {},
|
|
38
|
+
:absolute_paths => {},
|
|
39
|
+
:options => {},
|
|
40
|
+
:logger => {
|
|
41
|
+
:device => STDOUT,
|
|
42
|
+
:level => 1, # corresponds to 'INFO'
|
|
43
|
+
:datetime_format => '%H:%M:%S'},
|
|
44
|
+
:load_paths => []
|
|
45
|
+
}
|
|
46
|
+
assert_equal expected, app.config
|
|
47
|
+
|
|
48
|
+
# now try with a variety of configurations changed
|
|
49
|
+
app.options.trace = true
|
|
50
|
+
app[:lib] = 'alt/lib'
|
|
51
|
+
app[:abs, true] = '/absolute/path'
|
|
52
|
+
strio = StringIO.new('')
|
|
53
|
+
app.logger = Logger.new(strio)
|
|
54
|
+
app.load_paths << "path"
|
|
55
|
+
|
|
56
|
+
expected = {
|
|
57
|
+
:root => Dir.pwd,
|
|
58
|
+
:directories => {:lib => 'alt/lib'},
|
|
59
|
+
:absolute_paths => {:abs => '/absolute/path'},
|
|
60
|
+
:options => {:trace => true},
|
|
61
|
+
:logger => {
|
|
62
|
+
:device => strio,
|
|
63
|
+
:level => 0,
|
|
64
|
+
:datetime_format => nil},
|
|
65
|
+
:load_paths => ["path"]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
assert_equal 0, app.logger.level
|
|
69
|
+
assert_equal nil, app.logger.datetime_format
|
|
70
|
+
assert_equal expected, app.config
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# reconfigure test
|
|
75
|
+
#
|
|
76
|
+
|
|
77
|
+
def test_reconfigure_may_overrride_root_directories_options_and_logging
|
|
78
|
+
app = App.new
|
|
79
|
+
|
|
80
|
+
assert_equal Dir.pwd, app.root
|
|
81
|
+
app.reconfigure :root => './alt/root'
|
|
82
|
+
assert_equal './alt/root', app.root
|
|
83
|
+
|
|
84
|
+
assert_equal({}, app.directories)
|
|
85
|
+
app.reconfigure :directories => {:lib => 'alt/lib'}
|
|
86
|
+
assert_equal({:lib => 'alt/lib'}, app.directories)
|
|
87
|
+
|
|
88
|
+
assert_equal({}, app.options.marshal_dump)
|
|
89
|
+
app.reconfigure :options => {:trace => true}
|
|
90
|
+
assert_equal({:trace => true}, app.options.marshal_dump)
|
|
91
|
+
|
|
92
|
+
assert_equal STDOUT, app.logger.logdev.dev
|
|
93
|
+
strio = StringIO.new('')
|
|
94
|
+
app.reconfigure :logger => {:device => strio, :level => Logger::WARN}
|
|
95
|
+
assert_equal strio, app.logger.logdev.dev
|
|
96
|
+
assert_equal Logger::WARN, app.logger.level
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# set logger tests
|
|
101
|
+
#
|
|
102
|
+
|
|
103
|
+
def test_set_logger_extends_logger_with_support_logger
|
|
104
|
+
output = StringIO.new('')
|
|
105
|
+
logger = Logger.new(output)
|
|
106
|
+
assert !logger.respond_to?(:section_break)
|
|
107
|
+
|
|
108
|
+
app.logger = logger
|
|
109
|
+
assert logger.respond_to?(:section_break)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
# TODO -- Add logging tests
|
|
114
|
+
#
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
# task tests
|
|
119
|
+
#
|
|
120
|
+
|
|
121
|
+
class TaskSubClass < Task
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_task_returns_task_if_a_task_is_provided
|
|
125
|
+
task = Task.new
|
|
126
|
+
assert_equal task, app.task(task)
|
|
127
|
+
|
|
128
|
+
subtask = TaskSubClass.new
|
|
129
|
+
assert_equal subtask, app.task(subtask)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_task_reconfigures_task_with_config
|
|
133
|
+
task = Task.new
|
|
134
|
+
config = {"key" => "value"}
|
|
135
|
+
|
|
136
|
+
assert_equal({}, task.config)
|
|
137
|
+
assert_equal task, app.task(task, config)
|
|
138
|
+
assert_equal(config.symbolize_keys, task.config)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_task_looks_up_and_instantiates_task_if_task_is_a_string
|
|
142
|
+
assert_equal TaskSubClass, app.task("AppTest::TaskSubClass").class
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_task_translates_strings_to_class_name_using_map_if_possible
|
|
146
|
+
app.map["mapped_name"] = "AppTest::TaskSubClass"
|
|
147
|
+
assert_equal TaskSubClass, app.task("mapped_name").class
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_task_translates_strings_to_class_name_using_camelize_by_default
|
|
151
|
+
assert_equal TaskSubClass, app.task("app_test/task_sub_class").class
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_task_name_and_version_is_respected
|
|
155
|
+
t = app.task("app_test/task_sub_class-1.1")
|
|
156
|
+
assert_equal TaskSubClass, t.class
|
|
157
|
+
assert_equal "app_test/task_sub_class-1.1", t.name
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_task_can_find_task_classes_along_Dependencies_load_paths
|
|
161
|
+
begin
|
|
162
|
+
Dependencies.load_paths << app['lib']
|
|
163
|
+
assert_equal AppTestTask, app.task("AppTestTask").class
|
|
164
|
+
ensure
|
|
165
|
+
Dependencies.load_paths.delete(app['lib'])
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_task_raises_error_if_class_cannot_be_found
|
|
170
|
+
assert_raise(RuntimeError) { app.task("NonExistant") }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# config_templates tests
|
|
175
|
+
#
|
|
176
|
+
|
|
177
|
+
def test_config_templates_retrieves_templates_for_the_specified_task_config_file
|
|
178
|
+
filepath = app.filepath('config', "version.yml")
|
|
179
|
+
assert File.exists?(filepath)
|
|
180
|
+
assert_equal [{"version" => "empty"}], app.config_templates(filepath)
|
|
181
|
+
|
|
182
|
+
filepath = app.filepath('config', "version-0.1.yml")
|
|
183
|
+
assert File.exists?(filepath)
|
|
184
|
+
assert_equal [{"version" => 0.1}], app.config_templates(filepath)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_config_templates_can_load_an_array_of_templates
|
|
188
|
+
filepath = app.filepath('config', "batch.yml")
|
|
189
|
+
assert File.exists?(filepath)
|
|
190
|
+
assert_equal [{"key" => "one"}, {"key" => "two"}], app.config_templates(filepath)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_config_templates_returns_empty_template_even_if_config_file_does_not_exist
|
|
194
|
+
filepath = app.filepath('config', "non_existant.yml")
|
|
195
|
+
assert !File.exists?(filepath)
|
|
196
|
+
assert_equal [{}], app.config_templates(filepath)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_config_templates_returns_empty_template_if_config_file_is_empty
|
|
200
|
+
filepath = app.filepath('config', "empty.yml")
|
|
201
|
+
assert File.exists?(filepath)
|
|
202
|
+
assert_equal "", File.read(filepath)
|
|
203
|
+
assert_equal [{}], app.config_templates(filepath)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def test_config_templates_templates_using_erb
|
|
207
|
+
filepath = app.filepath('config', "erb.yml")
|
|
208
|
+
assert_equal [{"sum" => 3}], app.config_templates(filepath)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# execute test
|
|
213
|
+
#
|
|
214
|
+
|
|
215
|
+
def test_execute_executes_task_with_inputs
|
|
216
|
+
t = Task.new(&add_one)
|
|
217
|
+
t.enq(1,2,3)
|
|
218
|
+
|
|
219
|
+
app.execute(t)
|
|
220
|
+
assert_inputs(t => [1,2,3])
|
|
221
|
+
assert_outputs(t => [2,3,4])
|
|
222
|
+
assert_equal [1,2,3], runlist
|
|
223
|
+
assert_audits(t.results,
|
|
224
|
+
0 => [[nil,1], [t,2]],
|
|
225
|
+
1 => [[nil,2], [t,3]],
|
|
226
|
+
2 => [[nil,3], [t,4]])
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
#
|
|
230
|
+
# run tests
|
|
231
|
+
#
|
|
232
|
+
|
|
233
|
+
def test_run_single_task
|
|
234
|
+
t = Task.new(&add_one)
|
|
235
|
+
app.run(t,1,2,3)
|
|
236
|
+
assert_inputs(t => [1,2,3])
|
|
237
|
+
assert_outputs(t => [2,3,4])
|
|
238
|
+
assert_equal [1,2,3], runlist
|
|
239
|
+
assert_audits(t.results,
|
|
240
|
+
0 => [[nil,1], [t,2]],
|
|
241
|
+
1 => [[nil,2], [t,3]],
|
|
242
|
+
2 => [[nil,3], [t,4]])
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def test_run_single_task_from_a_thread
|
|
246
|
+
t = Task.new(&add_one)
|
|
247
|
+
|
|
248
|
+
th = Thread.new { app.run(t,1,2,3) }
|
|
249
|
+
th.join
|
|
250
|
+
|
|
251
|
+
assert_inputs(t => [1,2,3])
|
|
252
|
+
assert_outputs(t => [2,3,4])
|
|
253
|
+
assert_equal [1,2,3], runlist
|
|
254
|
+
assert_audits(t.results,
|
|
255
|
+
0 => [[nil,1], [t,2]],
|
|
256
|
+
1 => [[nil,2], [t,3]],
|
|
257
|
+
2 => [[nil,3], [t,4]])
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
#
|
|
261
|
+
# multithread test
|
|
262
|
+
#
|
|
263
|
+
|
|
264
|
+
def test_main_thread_execution_waits_for_multithread_execution
|
|
265
|
+
extended_test do
|
|
266
|
+
main_thread_ran = false
|
|
267
|
+
multithread_ran = false
|
|
268
|
+
|
|
269
|
+
multithread_executing = false
|
|
270
|
+
main_thread_did_not_wait = false
|
|
271
|
+
|
|
272
|
+
t1 = Task.new do |task, input|
|
|
273
|
+
main_thread_ran = true
|
|
274
|
+
main_thread_did_not_wait = true if multithread_executing
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
t2 = Task.new do |task, input|
|
|
278
|
+
multithread_ran = true
|
|
279
|
+
multithread_executing = true
|
|
280
|
+
|
|
281
|
+
# sleep is necessary so the other threads
|
|
282
|
+
# have an opportunity to execute
|
|
283
|
+
sleep(0.2)
|
|
284
|
+
|
|
285
|
+
multithread_executing = false
|
|
286
|
+
end
|
|
287
|
+
t2.multithread = true
|
|
288
|
+
|
|
289
|
+
t2.enq nil
|
|
290
|
+
t1.enq nil
|
|
291
|
+
|
|
292
|
+
app.run
|
|
293
|
+
|
|
294
|
+
assert main_thread_ran
|
|
295
|
+
assert multithread_ran
|
|
296
|
+
assert !main_thread_did_not_wait
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def test_multithread_execution_waits_for_main_thread_execution
|
|
301
|
+
extended_test do
|
|
302
|
+
main_thread_ran = false
|
|
303
|
+
multithread_ran = false
|
|
304
|
+
|
|
305
|
+
multithread_did_not_wait = false
|
|
306
|
+
main_thread_executing = false
|
|
307
|
+
|
|
308
|
+
t1 = Task.new do |task, input|
|
|
309
|
+
multithread_ran = true
|
|
310
|
+
multithread_did_not_wait = true if main_thread_executing
|
|
311
|
+
end
|
|
312
|
+
t1.multithread = true
|
|
313
|
+
|
|
314
|
+
t2 = Task.new do |task, input|
|
|
315
|
+
main_thread_ran = true
|
|
316
|
+
main_thread_executing = true
|
|
317
|
+
|
|
318
|
+
# sleep is necessary so the other threads
|
|
319
|
+
# have an opportunity to execute
|
|
320
|
+
sleep(0.2)
|
|
321
|
+
|
|
322
|
+
main_thread_executing = false
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
t1.enq nil
|
|
326
|
+
t2.enq nil
|
|
327
|
+
|
|
328
|
+
app.run
|
|
329
|
+
|
|
330
|
+
assert main_thread_ran
|
|
331
|
+
assert multithread_ran
|
|
332
|
+
assert !multithread_did_not_wait
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def test_only_max_threads_will_be_executed_at_a_time
|
|
337
|
+
extended_test do
|
|
338
|
+
max_threads = 0
|
|
339
|
+
n_threads = 0
|
|
340
|
+
|
|
341
|
+
# the logic of this test is that IF app executes
|
|
342
|
+
# in a multithreaded manner, then max_threads will
|
|
343
|
+
# be greater than 1. Furthermore, IF the :max_threads
|
|
344
|
+
# option is respected, then max_threads shouldn't be
|
|
345
|
+
# greater than 2.
|
|
346
|
+
block = lambda do
|
|
347
|
+
n_threads += 1
|
|
348
|
+
max_threads = n_threads if n_threads > max_threads
|
|
349
|
+
|
|
350
|
+
# sleep is necessary so the other threads
|
|
351
|
+
# have an opportunity to execute
|
|
352
|
+
sleep(0.2)
|
|
353
|
+
|
|
354
|
+
n_threads -= 1
|
|
355
|
+
nil
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
t1 = Task.new(&block)
|
|
359
|
+
t2 = Task.new(&block)
|
|
360
|
+
t3 = Task.new(&block)
|
|
361
|
+
|
|
362
|
+
with_options(:max_threads => 2) do
|
|
363
|
+
t1.enq nil
|
|
364
|
+
t2.enq nil
|
|
365
|
+
t3.enq nil
|
|
366
|
+
app.multithread(t1,t2,t3)
|
|
367
|
+
|
|
368
|
+
app.run
|
|
369
|
+
|
|
370
|
+
assert_equal 2, max_threads
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def test_a_multithreaded_task_executes_only_on_one_thread_at_a_time
|
|
376
|
+
extended_test do
|
|
377
|
+
max_threads = 0
|
|
378
|
+
n_threads = 0
|
|
379
|
+
count = 0
|
|
380
|
+
threads = []
|
|
381
|
+
|
|
382
|
+
# if multithread execution happened, then max_threads will be greater than 1.
|
|
383
|
+
# (here we want max_threads to be == 1 indicating t1 was only executed on one
|
|
384
|
+
# task at a time, even though it may have been executed on multiple threads)
|
|
385
|
+
t1 = Task.new do |task, input|
|
|
386
|
+
n_threads += 1
|
|
387
|
+
max_threads = n_threads if n_threads > max_threads
|
|
388
|
+
|
|
389
|
+
threads << Thread.current
|
|
390
|
+
count += 1
|
|
391
|
+
task.enq nil if count < 3
|
|
392
|
+
|
|
393
|
+
# sleep is necessary so the other threads
|
|
394
|
+
# have an opportunity to execute
|
|
395
|
+
sleep(0.2)
|
|
396
|
+
|
|
397
|
+
n_threads -= 1
|
|
398
|
+
nil
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
t1.enq nil
|
|
402
|
+
t1.multithread = true
|
|
403
|
+
|
|
404
|
+
app.run
|
|
405
|
+
|
|
406
|
+
assert max_threads == 1
|
|
407
|
+
assert count == 3
|
|
408
|
+
assert threads[0] != threads[1]
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def test_multithreaded_tasks_execute_in_order
|
|
413
|
+
extended_test do
|
|
414
|
+
max_threads = 0
|
|
415
|
+
n_threads = 0
|
|
416
|
+
runlist = []
|
|
417
|
+
|
|
418
|
+
# if the :max_threads option is respected, then max_threads
|
|
419
|
+
# shouldn't be greater than 2. if multithread execution
|
|
420
|
+
# happened, then max_threads will be greater than 1.
|
|
421
|
+
block = lambda do |task, input|
|
|
422
|
+
runlist << input
|
|
423
|
+
|
|
424
|
+
n_threads += 1
|
|
425
|
+
max_threads = n_threads if n_threads > max_threads
|
|
426
|
+
|
|
427
|
+
# sleep is necessary so the other threads
|
|
428
|
+
# have an opportunity to execute
|
|
429
|
+
sleep(0.2)
|
|
430
|
+
|
|
431
|
+
n_threads -= 1
|
|
432
|
+
nil
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
t1 = Task.new(&block)
|
|
436
|
+
t2 = Task.new(&block)
|
|
437
|
+
t3 = Task.new(&block)
|
|
438
|
+
|
|
439
|
+
with_options(:max_threads => 2) do
|
|
440
|
+
t1.enq 1, 2
|
|
441
|
+
t2.enq 3
|
|
442
|
+
t3.enq 4
|
|
443
|
+
app.multithread(t1,t2,t3)
|
|
444
|
+
|
|
445
|
+
app.run
|
|
446
|
+
|
|
447
|
+
assert_equal 2, max_threads
|
|
448
|
+
# t1 passes [1,2] as inputs
|
|
449
|
+
# t1(1) executes, then pauses, leading to
|
|
450
|
+
# t2(3) executing. Next t1 unpauses to execute
|
|
451
|
+
# t1(2) because no more threads are available. Lastly
|
|
452
|
+
# t3(4) executes
|
|
453
|
+
assert_equal [1,3,2,4], runlist
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def current_threads
|
|
459
|
+
threads = []
|
|
460
|
+
ObjectSpace.garbage_collect
|
|
461
|
+
sleep 0.2 # sleep to give garbage collect time to run
|
|
462
|
+
ObjectSpace.each_object(Thread) {|t| threads << t.object_id}
|
|
463
|
+
threads
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def extended_test_with_thread_check
|
|
467
|
+
extended_test do
|
|
468
|
+
prior_threads = current_threads
|
|
469
|
+
yield
|
|
470
|
+
assert_equal prior_threads, current_threads
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def test_threads_are_clear_after_clean_multithread_exit
|
|
475
|
+
extended_test_with_thread_check do
|
|
476
|
+
max_threads = 0
|
|
477
|
+
n_threads = 0
|
|
478
|
+
|
|
479
|
+
tasks = Array.new(3) do
|
|
480
|
+
Task.new do |task, inputs|
|
|
481
|
+
n_threads += 1
|
|
482
|
+
max_threads = n_threads if n_threads > max_threads
|
|
483
|
+
sleep 0.2
|
|
484
|
+
n_threads -= 1
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
tasks.each do |task|
|
|
488
|
+
task.multithread = true
|
|
489
|
+
task.enq nil
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
app.run
|
|
493
|
+
|
|
494
|
+
assert_equal 3, max_threads
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def test_threads_are_clear_after_clean_main_thread_exit
|
|
499
|
+
extended_test_with_thread_check do
|
|
500
|
+
max_threads = 0
|
|
501
|
+
n_threads = 0
|
|
502
|
+
|
|
503
|
+
tasks = Array.new(3) do
|
|
504
|
+
Task.new do |task, inputs|
|
|
505
|
+
n_threads += 1
|
|
506
|
+
max_threads = n_threads if n_threads > max_threads
|
|
507
|
+
sleep 0.2
|
|
508
|
+
n_threads -= 1
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
non_threaded = tasks.shift
|
|
512
|
+
tasks.each do |task|
|
|
513
|
+
task.multithread = true
|
|
514
|
+
task.enq nil
|
|
515
|
+
end
|
|
516
|
+
non_threaded.enq nil
|
|
517
|
+
|
|
518
|
+
app.run
|
|
519
|
+
|
|
520
|
+
assert_equal 2, max_threads
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
#
|
|
526
|
+
# stop test
|
|
527
|
+
#
|
|
528
|
+
|
|
529
|
+
def test_stop_prevents_non_executing_tasks_from_executing_on_main_thread
|
|
530
|
+
extended_test do
|
|
531
|
+
count = 0
|
|
532
|
+
tasks = Array.new(5) do
|
|
533
|
+
Task.new do |task, inputs|
|
|
534
|
+
count += 1
|
|
535
|
+
app.stop if count == 2
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
tasks.each do |task|
|
|
539
|
+
task.enq nil
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# under these conditions, 2 tasks should be
|
|
543
|
+
# executed on 2 threads, and 2 additional tasks
|
|
544
|
+
# dequeued into the thread queue. on stop, the 2
|
|
545
|
+
# executing tasks should finish normally, and NO MORE
|
|
546
|
+
# tasks executed. The waiting tasks will be requeued.
|
|
547
|
+
with_options :max_threads => 2 do
|
|
548
|
+
app.run
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
assert_equal 2, count
|
|
552
|
+
assert_equal 3, app.queue.size
|
|
553
|
+
|
|
554
|
+
queued_tasks = []
|
|
555
|
+
while !app.queue.empty?
|
|
556
|
+
task, inputs = app.queue.deq
|
|
557
|
+
queued_tasks << task.object_id
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# check that the requeued tasks are in order
|
|
561
|
+
assert_equal tasks[2...5].collect {|t| t.object_id}, queued_tasks
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def test_stop_prevents_non_executing_tasks_from_executing_on_threads_and_requeues_thread_queue
|
|
566
|
+
extended_test do
|
|
567
|
+
count = 0
|
|
568
|
+
tasks = Array.new(5) do
|
|
569
|
+
Task.new do |task, inputs|
|
|
570
|
+
count += 1
|
|
571
|
+
sleep 0.2
|
|
572
|
+
app.stop if count == 2
|
|
573
|
+
sleep 0.2
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
tasks.each do |task|
|
|
577
|
+
task.multithread = true
|
|
578
|
+
task.enq nil
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# under these conditions, 2 tasks should be
|
|
582
|
+
# executed on 2 threads, and 2 additional tasks
|
|
583
|
+
# dequeued into the thread queue. on stop, the 2
|
|
584
|
+
# executing tasks should finish normally, and NO MORE
|
|
585
|
+
# tasks executed. The waiting tasks will be requeued.
|
|
586
|
+
with_options :max_threads => 2 do
|
|
587
|
+
app.run
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
assert_equal 2, count
|
|
591
|
+
assert_equal 3, app.queue.size
|
|
592
|
+
|
|
593
|
+
queued_tasks = []
|
|
594
|
+
while !app.queue.empty?
|
|
595
|
+
task, inputs = app.queue.deq
|
|
596
|
+
queued_tasks << task.object_id
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# check that the requeued tasks are in order
|
|
600
|
+
assert_equal tasks[2...5].collect {|t| t.object_id}, queued_tasks
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
#
|
|
605
|
+
# terminate test
|
|
606
|
+
#
|
|
607
|
+
|
|
608
|
+
def test_terminate_from_main_thread_raises_run_error
|
|
609
|
+
extended_test do
|
|
610
|
+
was_not_terminated = false
|
|
611
|
+
task = Task.new do |task, inputs|
|
|
612
|
+
app.terminate
|
|
613
|
+
was_not_terminated = true
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
task.enq nil
|
|
617
|
+
with_options :debug => true do
|
|
618
|
+
begin
|
|
619
|
+
app.run
|
|
620
|
+
flunk "no error was raised"
|
|
621
|
+
rescue
|
|
622
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
623
|
+
assert $!.original_error.kind_of?(Tap::App::TerminateError)
|
|
624
|
+
assert $!.terminate_errors.empty?
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
assert !was_not_terminated
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def test_terminate_from_main_thread_when_error_is_handled_still_raises_error
|
|
633
|
+
extended_test do
|
|
634
|
+
terminate_error_handled = false
|
|
635
|
+
task = Task.new do |task, inputs|
|
|
636
|
+
begin
|
|
637
|
+
app.terminate
|
|
638
|
+
rescue
|
|
639
|
+
terminate_error_handled = true
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
task.enq nil
|
|
644
|
+
with_options :debug => true do
|
|
645
|
+
begin
|
|
646
|
+
app.run
|
|
647
|
+
flunk "no error was raised"
|
|
648
|
+
rescue
|
|
649
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
650
|
+
assert $!.original_error.kind_of?(Tap::App::TerminateError)
|
|
651
|
+
assert $!.terminate_errors.empty?
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
assert terminate_error_handled
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def test_terminate_raises_error_on_each_execution_thread_and_requeues_thread_queue
|
|
660
|
+
extended_test do
|
|
661
|
+
count = 0
|
|
662
|
+
some_thread_was_not_terminated = false
|
|
663
|
+
tasks = Array.new(5) do
|
|
664
|
+
Task.new do |task, inputs|
|
|
665
|
+
count += 1
|
|
666
|
+
app.terminate if count == 2
|
|
667
|
+
sleep 2
|
|
668
|
+
some_thread_was_not_terminated = true
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
tasks.each do |task|
|
|
672
|
+
task.multithread = true
|
|
673
|
+
task.enq nil
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
# under these conditions, 2 tasks should be
|
|
677
|
+
# executed on 2 threads, and 2 additional tasks
|
|
678
|
+
# dequeued into the thread queue. on stop, the 2
|
|
679
|
+
# executing tasks should be terminated, and NO MORE
|
|
680
|
+
# tasks executed. The waiting tasks will be requeued.
|
|
681
|
+
with_options :max_threads => 2, :debug => true do
|
|
682
|
+
begin
|
|
683
|
+
app.run
|
|
684
|
+
flunk "no error was raised"
|
|
685
|
+
rescue
|
|
686
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
687
|
+
assert $!.original_error.kind_of?(Tap::App::TerminateError)
|
|
688
|
+
assert $!.terminate_errors.empty?
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
assert_equal 2, count
|
|
693
|
+
assert_equal 3, app.queue.size
|
|
694
|
+
assert !some_thread_was_not_terminated
|
|
695
|
+
|
|
696
|
+
queued_tasks = []
|
|
697
|
+
while !app.queue.empty?
|
|
698
|
+
task, inputs = app.queue.deq
|
|
699
|
+
queued_tasks << task.object_id
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# check that the requeued tasks are in order
|
|
703
|
+
assert_equal tasks[2...5].collect {|t| t.object_id}, queued_tasks
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def test_terminate_on_thread_when_error_is_handled_still_raises_error
|
|
708
|
+
extended_test do
|
|
709
|
+
count = 0
|
|
710
|
+
some_thread_was_not_terminated = false
|
|
711
|
+
handled_count = 0
|
|
712
|
+
|
|
713
|
+
tasks = Array.new(5) do
|
|
714
|
+
Task.new do |task, inputs|
|
|
715
|
+
count += 1
|
|
716
|
+
begin
|
|
717
|
+
app.terminate if count == 2
|
|
718
|
+
sleep 2
|
|
719
|
+
some_thread_was_not_terminated = true
|
|
720
|
+
rescue
|
|
721
|
+
handled_count += 1
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
tasks.each do |task|
|
|
726
|
+
task.multithread = true
|
|
727
|
+
task.enq nil
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# under these conditions, 2 tasks should be
|
|
731
|
+
# executed on 2 threads, and 2 additional tasks
|
|
732
|
+
# dequeued into the thread queue. on stop, the 2
|
|
733
|
+
# executing tasks should be terminated, and NO MORE
|
|
734
|
+
# tasks executed. The waiting tasks will be requeued.
|
|
735
|
+
with_options :max_threads => 2, :debug => true do
|
|
736
|
+
begin
|
|
737
|
+
app.run
|
|
738
|
+
flunk "no error was raised"
|
|
739
|
+
rescue
|
|
740
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
741
|
+
assert $!.original_error.kind_of?(Tap::App::TerminateError)
|
|
742
|
+
assert $!.terminate_errors.empty?
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
assert !some_thread_was_not_terminated
|
|
747
|
+
assert_equal 2, count
|
|
748
|
+
assert_equal 2, handled_count
|
|
749
|
+
assert_equal 3, app.queue.size
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
#
|
|
754
|
+
# info tests
|
|
755
|
+
#
|
|
756
|
+
|
|
757
|
+
def test_info_provides_information_string
|
|
758
|
+
assert_equal 'state: 0 (READY) queue: 0 waiting: 0 (0) threads: 0', app.info
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
def test_info_can_be_called_during_a_run
|
|
762
|
+
extended_test do
|
|
763
|
+
count = 0
|
|
764
|
+
info_str = nil
|
|
765
|
+
|
|
766
|
+
tasks = Array.new(5) do
|
|
767
|
+
Task.new do |task, inputs|
|
|
768
|
+
count += 1
|
|
769
|
+
info_str = app.info if count == 2
|
|
770
|
+
app.stop if count == 2
|
|
771
|
+
sleep 0.2
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
tasks.each do |task|
|
|
775
|
+
task.enq nil
|
|
776
|
+
task.multithread = true
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
with_options :max_threads => 2 do
|
|
780
|
+
app.run
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
assert_equal 'state: 1 (RUN) queue: 3 waiting: 3 (0) threads: 2', info_str
|
|
784
|
+
assert_equal 'state: 0 (READY) queue: 3 waiting: 3 (0) threads: 0', app.info
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
#
|
|
789
|
+
# sequence tests
|
|
790
|
+
#
|
|
791
|
+
|
|
792
|
+
def test_run_sequence
|
|
793
|
+
t1 = Task.new(&add_one)
|
|
794
|
+
t2 = Task.new(&add_one)
|
|
795
|
+
|
|
796
|
+
app.sequence(t1,t2)
|
|
797
|
+
app.run(t1,1,2,3)
|
|
798
|
+
|
|
799
|
+
assert_inputs(t1 => [1,2,3], t2 => [2,3,4])
|
|
800
|
+
assert_outputs(t1 => [2,3,4], t2 => [3,4,5])
|
|
801
|
+
assert_equal [1,2,3,2,3,4], runlist
|
|
802
|
+
assert_audits(t2.results,
|
|
803
|
+
0 => [[nil,1], [t1,2], [t2,3]],
|
|
804
|
+
1 => [[nil,2], [t1,3], [t2,4]],
|
|
805
|
+
2 => [[nil,3], [t1,4], [t2,5]])
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def test_run_sequence_from_trailing_task
|
|
809
|
+
t1 = Task.new(&add_one)
|
|
810
|
+
t2 = Task.new(&add_one)
|
|
811
|
+
|
|
812
|
+
app.sequence(t1,t2)
|
|
813
|
+
app.run(t2,1,2,3)
|
|
814
|
+
|
|
815
|
+
assert_inputs(t1 => [], t2 => [1,2,3])
|
|
816
|
+
assert_outputs(t1 => [], t2 => [2,3,4])
|
|
817
|
+
assert_equal [1,2,3], runlist
|
|
818
|
+
assert_audits(t2.results,
|
|
819
|
+
0 => [[nil,1], [t2,2]],
|
|
820
|
+
1 => [[nil,2], [t2,3]],
|
|
821
|
+
2 => [[nil,3], [t2,4]])
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
#
|
|
825
|
+
# fork tests
|
|
826
|
+
#
|
|
827
|
+
|
|
828
|
+
def test_run_fork
|
|
829
|
+
t1 = Task.new(&add_one)
|
|
830
|
+
t2 = Task.new(&add_one)
|
|
831
|
+
t3 = Task.new(&add_one)
|
|
832
|
+
|
|
833
|
+
app.fork(t1, t2, t3)
|
|
834
|
+
app.run(t1,1,2,3)
|
|
835
|
+
|
|
836
|
+
assert_inputs(t1 => [1,2,3], t2 => [2,3,4], t3 => [2,3,4])
|
|
837
|
+
assert_outputs(t1 => [2,3,4], t2 => [3,4,5], t3 => [3,4,5])
|
|
838
|
+
assert_equal [1,2,3, 2,3,4, 2,3,4], runlist
|
|
839
|
+
assert_audits(t2.results,
|
|
840
|
+
0 => [[nil,1], [t1,2], [t2,3]],
|
|
841
|
+
1 => [[nil,2], [t1,3], [t2,4]],
|
|
842
|
+
2 => [[nil,3], [t1,4], [t2,5]])
|
|
843
|
+
assert_audits(t3.results,
|
|
844
|
+
0 => [[nil,1], [t1,2], [t3,3]],
|
|
845
|
+
1 => [[nil,2], [t1,3], [t3,4]],
|
|
846
|
+
2 => [[nil,3], [t1,4], [t3,5]])
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
#
|
|
850
|
+
# merge tests
|
|
851
|
+
#
|
|
852
|
+
|
|
853
|
+
def test_run_merge
|
|
854
|
+
t1 = Task.new(&add_one)
|
|
855
|
+
t2 = Task.new(&add_one)
|
|
856
|
+
t3 = Task.new(&add_one)
|
|
857
|
+
|
|
858
|
+
# merge by default has no conditions on execution
|
|
859
|
+
# so t3 should execute immediately after t1/t2 finishes
|
|
860
|
+
# as such the results need to be collected separately
|
|
861
|
+
t3_results = []
|
|
862
|
+
t3.on_complete do |results|
|
|
863
|
+
t3_results.concat results
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
app.merge(t3, t1, t2)
|
|
867
|
+
app.run(t1,1,2,3)
|
|
868
|
+
app.run(t2,2,3,4)
|
|
869
|
+
|
|
870
|
+
assert_inputs(t1 => [1,2,3], t2 => [2,3,4])
|
|
871
|
+
assert_outputs(t1 => [2,3,4], t2 => [3,4,5])
|
|
872
|
+
assert_equal [1,2,3, 2,3,4, 2,3,4, 3,4,5], runlist
|
|
873
|
+
assert_audits(t3_results,
|
|
874
|
+
# from t1...
|
|
875
|
+
0 => [[nil,1], [t1,2], [t3,3]],
|
|
876
|
+
1 => [[nil,2], [t1,3], [t3,4]],
|
|
877
|
+
2 => [[nil,3], [t1,4], [t3,5]],
|
|
878
|
+
# from t2...
|
|
879
|
+
3 => [[nil,2], [t2,3], [t3,4]],
|
|
880
|
+
4 => [[nil,3], [t2,4], [t3,5]],
|
|
881
|
+
5 => [[nil,4], [t2,5], [t3,6]])
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
#
|
|
885
|
+
# run batched task tests
|
|
886
|
+
#
|
|
887
|
+
|
|
888
|
+
def test_run_batched_task
|
|
889
|
+
t1 = Task.new('addition_template') do |task, input|
|
|
890
|
+
runlist << input
|
|
891
|
+
input + task.config[:factor0] + task.config[:factor1]
|
|
892
|
+
end
|
|
893
|
+
assert_equal 2, t1.batch.length
|
|
894
|
+
|
|
895
|
+
t1_0 = t1.batch[0]
|
|
896
|
+
t1_1 = t1.batch[1]
|
|
897
|
+
|
|
898
|
+
assert_equal [0, 10], [t1_0.config[:factor0], t1_0.config[:factor1]]
|
|
899
|
+
assert_equal [1, 20], [t1_1.config[:factor0], t1_1.config[:factor1]]
|
|
900
|
+
|
|
901
|
+
app.run(t1,1,2,3)
|
|
902
|
+
|
|
903
|
+
# note same inputs fed to each template
|
|
904
|
+
assert_equal [1,2,3, 1,2,3], runlist
|
|
905
|
+
assert_audits(t1_0.results,
|
|
906
|
+
0 => [[nil,1], [t1_0,11]],
|
|
907
|
+
1 => [[nil,2], [t1_0,12]],
|
|
908
|
+
2 => [[nil,3], [t1_0,13]])
|
|
909
|
+
assert_audits(t1_1.results,
|
|
910
|
+
0 => [[nil,1], [t1_1,22]],
|
|
911
|
+
1 => [[nil,2], [t1_1,23]],
|
|
912
|
+
2 => [[nil,3], [t1_1,24]])
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
def test_run_batched_task_with_existing_audit_trails
|
|
916
|
+
t1 = Task.new('addition_template') do |task, input|
|
|
917
|
+
runlist << input
|
|
918
|
+
input + task.config[:factor0] + task.config[:factor1]
|
|
919
|
+
end
|
|
920
|
+
assert_equal 2, t1.batch.length
|
|
921
|
+
|
|
922
|
+
t1_0 = t1.batch[0]
|
|
923
|
+
t1_1 = t1.batch[1]
|
|
924
|
+
|
|
925
|
+
assert_equal [0, 10], [t1_0.config[:factor0], t1_0.config[:factor1]]
|
|
926
|
+
assert_equal [1, 20], [t1_1.config[:factor0], t1_1.config[:factor1]]
|
|
927
|
+
|
|
928
|
+
a = Support::Audit.new(1)
|
|
929
|
+
b = Support::Audit.new(2)
|
|
930
|
+
c = Support::Audit.new(3)
|
|
931
|
+
app.run(t1,a,b,c)
|
|
932
|
+
|
|
933
|
+
# note same inputs fed to each template
|
|
934
|
+
assert_equal [1,2,3, 1,2,3], runlist
|
|
935
|
+
assert_audits(t1_0.results,
|
|
936
|
+
0 => [[nil,1], [t1_0,11]],
|
|
937
|
+
1 => [[nil,2], [t1_0,12]],
|
|
938
|
+
2 => [[nil,3], [t1_0,13]])
|
|
939
|
+
assert_audits(t1_1.results,
|
|
940
|
+
0 => [[nil,1], [t1_1,22]],
|
|
941
|
+
1 => [[nil,2], [t1_1,23]],
|
|
942
|
+
2 => [[nil,3], [t1_1,24]])
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
def test_multithread_batched_tasks_execute_cosynchronously
|
|
946
|
+
extended_test do
|
|
947
|
+
max_threads = 0
|
|
948
|
+
n_threads = 0
|
|
949
|
+
|
|
950
|
+
block = lambda do
|
|
951
|
+
n_threads += 1
|
|
952
|
+
max_threads = n_threads if n_threads > max_threads
|
|
953
|
+
|
|
954
|
+
# sleep is necessary so the other threads
|
|
955
|
+
# have an opportunity to execute
|
|
956
|
+
sleep(0.2)
|
|
957
|
+
|
|
958
|
+
n_threads -= 1
|
|
959
|
+
nil
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
t1 = Task.new('addition_template', &block)
|
|
963
|
+
|
|
964
|
+
assert_equal 2, t1.batch.length
|
|
965
|
+
t1.batch.each do |task|
|
|
966
|
+
task.multithread = true
|
|
967
|
+
task.enq nil
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
app.run
|
|
971
|
+
assert_equal 2, max_threads
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
def test_fork_in_batched_task
|
|
976
|
+
t1, t2, t3 = Array.new(3) do
|
|
977
|
+
Task.new('addition_template') do |task, input|
|
|
978
|
+
runlist << input
|
|
979
|
+
input + task.config[:factor0] + task.config[:factor1]
|
|
980
|
+
end
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
app.fork(t1, t2, t3)
|
|
984
|
+
app.run(t1, 1,2)
|
|
985
|
+
|
|
986
|
+
assert_equal [
|
|
987
|
+
1,2,1,2, # once for each t1 template
|
|
988
|
+
11,12,22,23, 11,12,22,23, # each result into each t2 template
|
|
989
|
+
11,12,22,23, 11,12,22,23 # each result into each t3 template
|
|
990
|
+
], runlist
|
|
991
|
+
|
|
992
|
+
# Note how all the inputs were registered to the tasks
|
|
993
|
+
# before execution... so all 4 inputs execute as a batch
|
|
994
|
+
# and the inputs execute for both templates
|
|
995
|
+
t1_0 = t1.batch[0]
|
|
996
|
+
t1_1 = t1.batch[1]
|
|
997
|
+
|
|
998
|
+
t3_0 = t3.batch[0]
|
|
999
|
+
assert_audits(t3_0.results,
|
|
1000
|
+
0 => [[nil,1], [t1_0,11], [t3_0, 21]],
|
|
1001
|
+
1 => [[nil,2], [t1_0,12], [t3_0, 22]],
|
|
1002
|
+
2 => [[nil,1], [t1_1,22], [t3_0, 32]],
|
|
1003
|
+
3 => [[nil,2], [t1_1,23], [t3_0, 33]])
|
|
1004
|
+
|
|
1005
|
+
t3_1 = t3.batch[1]
|
|
1006
|
+
assert_audits(t3_1.results,
|
|
1007
|
+
0 => [[nil,1], [t1_0,11], [t3_1, 32]],
|
|
1008
|
+
1 => [[nil,2], [t1_0,12], [t3_1, 33]],
|
|
1009
|
+
2 => [[nil,1], [t1_1,22], [t3_1, 43]],
|
|
1010
|
+
3 => [[nil,2], [t1_1,23], [t3_1, 44]])
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
def test_merge_batched_task
|
|
1014
|
+
t1, t2, t3 = Array.new(3) do
|
|
1015
|
+
Task.new('addition_template') do |task, input|
|
|
1016
|
+
runlist << input
|
|
1017
|
+
input + task.config[:factor0] + task.config[:factor1]
|
|
1018
|
+
end
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
app.merge(t3, t1, t2)
|
|
1022
|
+
t1.enq(1,2)
|
|
1023
|
+
t2.enq(3,4)
|
|
1024
|
+
app.run
|
|
1025
|
+
|
|
1026
|
+
assert_equal [
|
|
1027
|
+
1,2,1,2, # 1,2 inputs to each t1
|
|
1028
|
+
3,4,3,4, # 3,4 inputs to each t2
|
|
1029
|
+
11,12,22,23, 13,14,24,25, # t1 then t2 outputs to first t3
|
|
1030
|
+
11,12,22,23, 13,14,24,25 # t1 then t2 outputs to second t3
|
|
1031
|
+
], runlist
|
|
1032
|
+
|
|
1033
|
+
t1_0 = t1.batch[0]
|
|
1034
|
+
t1_1 = t1.batch[1]
|
|
1035
|
+
t2_0 = t2.batch[0]
|
|
1036
|
+
t2_1 = t2.batch[1]
|
|
1037
|
+
t3_0 = t3.batch[0]
|
|
1038
|
+
assert_audits(t3_0.results,
|
|
1039
|
+
# t1 0
|
|
1040
|
+
0 => [[nil,1], [t1_0,11], [t3_0,21]],
|
|
1041
|
+
1 => [[nil,2], [t1_0,12], [t3_0,22]],
|
|
1042
|
+
# t1 1
|
|
1043
|
+
2 => [[nil,1], [t1_1,22], [t3_0,32]],
|
|
1044
|
+
3 => [[nil,2], [t1_1,23], [t3_0,33]],
|
|
1045
|
+
# t2 0
|
|
1046
|
+
4 => [[nil,3], [t2_0,13], [t3_0,23]],
|
|
1047
|
+
5 => [[nil,4], [t2_0,14], [t3_0,24]],
|
|
1048
|
+
# t2 1
|
|
1049
|
+
6 => [[nil,3], [t2_1,24], [t3_0,34]],
|
|
1050
|
+
7 => [[nil,4], [t2_1,25], [t3_0,35]])
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
#
|
|
1054
|
+
# on_complete tests
|
|
1055
|
+
#
|
|
1056
|
+
|
|
1057
|
+
def test_on_complete
|
|
1058
|
+
t1 = Task.new(&add_one)
|
|
1059
|
+
t2 = Task.new(&add_one)
|
|
1060
|
+
t3 = Task.new(&add_one)
|
|
1061
|
+
|
|
1062
|
+
app.on_complete(t1) do |results|
|
|
1063
|
+
t2.enq results.first
|
|
1064
|
+
t3.enq results.last
|
|
1065
|
+
end
|
|
1066
|
+
app.run(t1,1,2,3)
|
|
1067
|
+
|
|
1068
|
+
assert_inputs(t1 => [1,2,3], t2 => [2], t3 => [4])
|
|
1069
|
+
assert_outputs(t1 => [2,3,4], t2 => [3], t3 => [5])
|
|
1070
|
+
assert_equal [1,2,3,2,4], runlist
|
|
1071
|
+
assert_audits(t1.results,
|
|
1072
|
+
0 => [[nil,1], [t1,2], [t2,3]],
|
|
1073
|
+
1 => [[nil,2], [t1,3]],
|
|
1074
|
+
2 => [[nil,3], [t1,4], [t3,5]])
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
def test_feedback_loop
|
|
1078
|
+
t1 = Task.new(&add_one)
|
|
1079
|
+
t2 = Task.new(&add_one)
|
|
1080
|
+
t3 = Task.new(&add_one)
|
|
1081
|
+
|
|
1082
|
+
# distribute the results of t1 based on value
|
|
1083
|
+
app.on_complete(t1) do |audits|
|
|
1084
|
+
audits.each do |audit|
|
|
1085
|
+
if audit < 4
|
|
1086
|
+
t2.enq audit
|
|
1087
|
+
else
|
|
1088
|
+
t3.enq audit
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
# set the results of t2 to reinvoke the workflow
|
|
1094
|
+
app.sequence(t2, t1)
|
|
1095
|
+
|
|
1096
|
+
# collect the results as they come to t3
|
|
1097
|
+
# (t3.results does not suffice because t3.results will be
|
|
1098
|
+
# reset on each invokation)
|
|
1099
|
+
t3_results = []
|
|
1100
|
+
app.on_complete(t3) {|audits| t3_results.concat(audits) }
|
|
1101
|
+
|
|
1102
|
+
app.run(t1,1,2,3)
|
|
1103
|
+
|
|
1104
|
+
assert_audits(t3_results,
|
|
1105
|
+
0 => [[nil,3], [t1,4], [t3,5]],
|
|
1106
|
+
1 => [[nil,1], [t1,2], [t2,3], [t1,4], [t3,5]],
|
|
1107
|
+
2 => [[nil,2], [t1,3], [t2,4], [t1,5], [t3,6]])
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
#
|
|
1111
|
+
# synchronization tests
|
|
1112
|
+
#
|
|
1113
|
+
|
|
1114
|
+
def BREAK_DOES_NOT_ACTUALLY_TEST_THIStest_run_is_synchronized
|
|
1115
|
+
extended_test do
|
|
1116
|
+
count = 0
|
|
1117
|
+
counter = Task.new do
|
|
1118
|
+
count += 1
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
t1 = Thread.new { 10000.times { app.run(counter, nil) } }
|
|
1122
|
+
t2 = Thread.new { 10000.times { app.run(counter, nil) } }
|
|
1123
|
+
t1.join
|
|
1124
|
+
t2.join
|
|
1125
|
+
|
|
1126
|
+
assert_equal 20000, count
|
|
1127
|
+
end
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
def test_task_may_be_queued_from_task_while_task_is_running
|
|
1131
|
+
count = 0
|
|
1132
|
+
counter = Task.new do
|
|
1133
|
+
count += 1
|
|
1134
|
+
counter.enq nil if count < 3
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
app.run(counter, nil)
|
|
1138
|
+
assert_equal 3, count
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
def test_task_can_queue_from_within_threaded_and_unthreaded_tasks
|
|
1142
|
+
threaded_count = 0
|
|
1143
|
+
threaded = Task.new do
|
|
1144
|
+
runlist << "t"
|
|
1145
|
+
threaded_count += 1
|
|
1146
|
+
threaded.enq nil if threaded_count < 3
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
not_threaded_count = 0
|
|
1150
|
+
not_threaded = Task.new do
|
|
1151
|
+
runlist << "n"
|
|
1152
|
+
not_threaded_count += 1
|
|
1153
|
+
not_threaded.enq nil if not_threaded_count < 3
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
app.multithread(threaded)
|
|
1157
|
+
threaded.enq nil
|
|
1158
|
+
not_threaded.enq nil
|
|
1159
|
+
|
|
1160
|
+
app.run
|
|
1161
|
+
|
|
1162
|
+
assert_equal [
|
|
1163
|
+
"t", "n",
|
|
1164
|
+
"t", "n",
|
|
1165
|
+
"t", "n"], runlist
|
|
1166
|
+
assert_equal 3, threaded_count
|
|
1167
|
+
assert_equal 3, not_threaded_count
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def test_execute_is_allowed_within_non_threaded_task
|
|
1171
|
+
# execute within a threaded task deadlocks
|
|
1172
|
+
t2 = Task.new(&add_one)
|
|
1173
|
+
t1 = Task.new do |task, input|
|
|
1174
|
+
runlist << input
|
|
1175
|
+
app.queue.enq(t2, input)
|
|
1176
|
+
app.execute(t2)
|
|
1177
|
+
|
|
1178
|
+
input += 1
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
app.run(t1, 1,2,3)
|
|
1182
|
+
|
|
1183
|
+
# remember t2, will only show the LAST input
|
|
1184
|
+
# to have been processed
|
|
1185
|
+
assert_inputs(t1 => [1,2,3], t2 => [3])
|
|
1186
|
+
assert_outputs(t1 => [2,3,4], t2 => [4])
|
|
1187
|
+
assert_equal [1,1,2,2,3,3], runlist
|
|
1188
|
+
assert_audits(t1.results,
|
|
1189
|
+
0 => [[nil,1], [t1,2]],
|
|
1190
|
+
1 => [[nil,2], [t1,3]],
|
|
1191
|
+
2 => [[nil,3], [t1,4]])
|
|
1192
|
+
assert_audits(t2.results,
|
|
1193
|
+
0 => [[nil,3], [t2,4]])
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
def test_run_is_allowed_within_non_threaded_task
|
|
1197
|
+
# run within a threaded task deadlocks
|
|
1198
|
+
t2 = Task.new(&add_one)
|
|
1199
|
+
t1 = Task.new do |task, input|
|
|
1200
|
+
runlist << input
|
|
1201
|
+
app.run(t2, input)
|
|
1202
|
+
|
|
1203
|
+
input += 1
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
app.run(t1, 1,2,3)
|
|
1207
|
+
|
|
1208
|
+
# remember t2, will only show the LAST input
|
|
1209
|
+
# to have been processed
|
|
1210
|
+
assert_inputs(t1 => [1,2,3], t2 => [3])
|
|
1211
|
+
assert_outputs(t1 => [2,3,4], t2 => [4])
|
|
1212
|
+
assert_equal [1,1,2,2,3,3], runlist
|
|
1213
|
+
assert_audits(t1.results,
|
|
1214
|
+
0 => [[nil,1], [t1,2]],
|
|
1215
|
+
1 => [[nil,2], [t1,3]],
|
|
1216
|
+
2 => [[nil,3], [t1,4]])
|
|
1217
|
+
assert_audits(t2.results,
|
|
1218
|
+
0 => [[nil,3], [t2,4]])
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
#
|
|
1222
|
+
# error tests
|
|
1223
|
+
#
|
|
1224
|
+
|
|
1225
|
+
def set_stringio_logger
|
|
1226
|
+
output = StringIO.new('')
|
|
1227
|
+
app.logger = Logger.new(output)
|
|
1228
|
+
output.string
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
def test_unhandled_exception_on_main_thread_is_logged_by_default
|
|
1232
|
+
t = Task.new {|t,i| raise "error"}
|
|
1233
|
+
|
|
1234
|
+
string = set_stringio_logger
|
|
1235
|
+
app.run(t, nil)
|
|
1236
|
+
|
|
1237
|
+
assert string =~ /RuntimeError error/
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
def test_unhandled_exception_raises_run_error_on_main_thread_when_debug
|
|
1241
|
+
t = Task.new {|t,i| raise "error"}
|
|
1242
|
+
|
|
1243
|
+
with_options :debug => true do
|
|
1244
|
+
begin
|
|
1245
|
+
app.run(t, nil)
|
|
1246
|
+
flunk "no error was raised"
|
|
1247
|
+
rescue
|
|
1248
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
1249
|
+
assert $!.original_error.kind_of?(RuntimeError)
|
|
1250
|
+
assert_equal "error", $!.original_error.message
|
|
1251
|
+
assert $!.terminate_errors.empty?
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
def test_unhandled_exception_on_thread_is_logged_by_default
|
|
1257
|
+
t = Task.new {|t,i| raise "error"}
|
|
1258
|
+
t.multithread = true
|
|
1259
|
+
|
|
1260
|
+
string = set_stringio_logger
|
|
1261
|
+
app.run(t, nil)
|
|
1262
|
+
|
|
1263
|
+
assert string =~ /RuntimeError error/
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
def test_unhandled_exception_raises_run_error_on_thread_when_debug
|
|
1267
|
+
t = Task.new {|t,i| raise "error"}
|
|
1268
|
+
t.multithread = true
|
|
1269
|
+
|
|
1270
|
+
with_options :debug => true do
|
|
1271
|
+
begin
|
|
1272
|
+
app.run(t, nil)
|
|
1273
|
+
flunk "no error was raised"
|
|
1274
|
+
rescue
|
|
1275
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
1276
|
+
assert $!.original_error.kind_of?(RuntimeError)
|
|
1277
|
+
assert_equal "error", $!.original_error.message
|
|
1278
|
+
assert $!.terminate_errors.empty?
|
|
1279
|
+
end
|
|
1280
|
+
end
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
def test_unhandled_exception_on_thread_teminates_threads
|
|
1284
|
+
extended_test do
|
|
1285
|
+
count = 0
|
|
1286
|
+
terminated_count = 0
|
|
1287
|
+
|
|
1288
|
+
tasks = Array.new(2) do
|
|
1289
|
+
Task.new do |t,i|
|
|
1290
|
+
# count to make sure the tasks actually executed
|
|
1291
|
+
count += 1
|
|
1292
|
+
|
|
1293
|
+
terminated_count += 1
|
|
1294
|
+
sleep 2
|
|
1295
|
+
|
|
1296
|
+
# this should not happen
|
|
1297
|
+
terminated_count -= 1
|
|
1298
|
+
end
|
|
1299
|
+
end
|
|
1300
|
+
terr = Task.new {|t,i| raise "error"}
|
|
1301
|
+
|
|
1302
|
+
tasks << terr
|
|
1303
|
+
tasks.each do |task|
|
|
1304
|
+
task.multithread = true
|
|
1305
|
+
task.enq nil
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
with_options :debug => true do
|
|
1309
|
+
begin
|
|
1310
|
+
app.run
|
|
1311
|
+
flunk "no error was raised"
|
|
1312
|
+
rescue
|
|
1313
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
1314
|
+
assert $!.original_error.kind_of?(RuntimeError)
|
|
1315
|
+
assert_equal "error", $!.original_error.message
|
|
1316
|
+
assert $!.terminate_errors.empty?
|
|
1317
|
+
end
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
assert_equal 2, count
|
|
1321
|
+
assert_equal 2, terminated_count
|
|
1322
|
+
end
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
def test_exceptions_from_handling_termination_error_are_collected
|
|
1326
|
+
extended_test do
|
|
1327
|
+
count = 0
|
|
1328
|
+
count_in_threaded_error_handling = 0
|
|
1329
|
+
|
|
1330
|
+
tasks = Array.new(2) do
|
|
1331
|
+
Task.new do |t,i|
|
|
1332
|
+
n = count
|
|
1333
|
+
begin
|
|
1334
|
+
count += 1
|
|
1335
|
+
sleep 2
|
|
1336
|
+
rescue
|
|
1337
|
+
count_in_threaded_error_handling += 1
|
|
1338
|
+
raise "term error #{n}"
|
|
1339
|
+
end
|
|
1340
|
+
end
|
|
1341
|
+
end
|
|
1342
|
+
terr = Task.new {|t,i| raise "error"}
|
|
1343
|
+
|
|
1344
|
+
tasks << terr
|
|
1345
|
+
tasks.each do |task|
|
|
1346
|
+
task.multithread = true
|
|
1347
|
+
task.enq nil
|
|
1348
|
+
end
|
|
1349
|
+
|
|
1350
|
+
with_options :debug => true do
|
|
1351
|
+
begin
|
|
1352
|
+
app.run
|
|
1353
|
+
flunk "no error was raised"
|
|
1354
|
+
rescue
|
|
1355
|
+
assert $!.kind_of?(Tap::Support::RunError)
|
|
1356
|
+
assert $!.original_error.kind_of?(RuntimeError)
|
|
1357
|
+
assert_equal "error", $!.original_error.message
|
|
1358
|
+
|
|
1359
|
+
assert_equal 2, $!.terminate_errors.length
|
|
1360
|
+
|
|
1361
|
+
$!.terminate_errors.each_with_index do |term_err, i|
|
|
1362
|
+
assert term_err.kind_of?(RuntimeError)
|
|
1363
|
+
assert_equal "term error #{i}", term_err.message
|
|
1364
|
+
end
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
assert_equal 2, count
|
|
1368
|
+
assert_equal 2, count_in_threaded_error_handling
|
|
1369
|
+
end
|
|
1370
|
+
end
|
|
1371
|
+
end
|
|
1372
|
+
end
|