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.
Files changed (146) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README +71 -0
  3. data/Rakefile +117 -0
  4. data/bin/tap +63 -0
  5. data/lib/tap.rb +15 -0
  6. data/lib/tap/app.rb +739 -0
  7. data/lib/tap/file_task.rb +354 -0
  8. data/lib/tap/generator.rb +29 -0
  9. data/lib/tap/generator/generators/config/USAGE +0 -0
  10. data/lib/tap/generator/generators/config/config_generator.rb +23 -0
  11. data/lib/tap/generator/generators/config/templates/config.erb +2 -0
  12. data/lib/tap/generator/generators/file_task/USAGE +0 -0
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
  16. data/lib/tap/generator/generators/root/USAGE +0 -0
  17. data/lib/tap/generator/generators/root/root_generator.rb +36 -0
  18. data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
  19. data/lib/tap/generator/generators/root/templates/app.yml +19 -0
  20. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
  21. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
  22. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  23. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
  24. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
  25. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
  26. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
  27. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
  28. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
  29. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
  30. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
  31. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
  32. data/lib/tap/generator/generators/task/USAGE +0 -0
  33. data/lib/tap/generator/generators/task/task_generator.rb +21 -0
  34. data/lib/tap/generator/generators/task/templates/task.erb +21 -0
  35. data/lib/tap/generator/generators/task/templates/test.erb +29 -0
  36. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  37. data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
  38. data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
  39. data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
  40. data/lib/tap/generator/options.rb +26 -0
  41. data/lib/tap/generator/usage.rb +26 -0
  42. data/lib/tap/root.rb +275 -0
  43. data/lib/tap/script/console.rb +7 -0
  44. data/lib/tap/script/destroy.rb +8 -0
  45. data/lib/tap/script/generate.rb +8 -0
  46. data/lib/tap/script/run.rb +111 -0
  47. data/lib/tap/script/server.rb +12 -0
  48. data/lib/tap/support/audit.rb +415 -0
  49. data/lib/tap/support/batch_queue.rb +165 -0
  50. data/lib/tap/support/combinator.rb +114 -0
  51. data/lib/tap/support/logger.rb +91 -0
  52. data/lib/tap/support/rap.rb +38 -0
  53. data/lib/tap/support/run_error.rb +20 -0
  54. data/lib/tap/support/template.rb +81 -0
  55. data/lib/tap/support/templater.rb +155 -0
  56. data/lib/tap/support/versions.rb +63 -0
  57. data/lib/tap/task.rb +448 -0
  58. data/lib/tap/test.rb +320 -0
  59. data/lib/tap/test/env_vars.rb +16 -0
  60. data/lib/tap/test/inference_methods.rb +298 -0
  61. data/lib/tap/test/subset_methods.rb +260 -0
  62. data/lib/tap/version.rb +3 -0
  63. data/lib/tap/workflow.rb +73 -0
  64. data/test/app/config/addition_template.yml +6 -0
  65. data/test/app/config/batch.yml +2 -0
  66. data/test/app/config/empty.yml +0 -0
  67. data/test/app/config/erb.yml +1 -0
  68. data/test/app/config/template.yml +6 -0
  69. data/test/app/config/version-0.1.yml +1 -0
  70. data/test/app/config/version.yml +1 -0
  71. data/test/app/lib/app_test_task.rb +2 -0
  72. data/test/app_class_test.rb +33 -0
  73. data/test/app_test.rb +1372 -0
  74. data/test/file_task/config/batch.yml +2 -0
  75. data/test/file_task/config/configured.yml +1 -0
  76. data/test/file_task/old_file_one.txt +0 -0
  77. data/test/file_task/old_file_two.txt +0 -0
  78. data/test/file_task_test.rb +1041 -0
  79. data/test/root/alt_lib/alt_module.rb +4 -0
  80. data/test/root/lib/absolute_alt_filepath.rb +2 -0
  81. data/test/root/lib/alternative_filepath.rb +2 -0
  82. data/test/root/lib/another_module.rb +2 -0
  83. data/test/root/lib/nested/some_module.rb +4 -0
  84. data/test/root/lib/no_module_included.rb +0 -0
  85. data/test/root/lib/some/module.rb +4 -0
  86. data/test/root/lib/some_class.rb +2 -0
  87. data/test/root/lib/some_module.rb +3 -0
  88. data/test/root/load_path/load_path_module.rb +2 -0
  89. data/test/root/load_path/skip_module.rb +2 -0
  90. data/test/root/mtime/older.txt +0 -0
  91. data/test/root/unload/full_path.rb +2 -0
  92. data/test/root/unload/loaded_by_nested.rb +2 -0
  93. data/test/root/unload/nested/nested_load.rb +6 -0
  94. data/test/root/unload/nested/nested_with_ext.rb +4 -0
  95. data/test/root/unload/nested/relative_path.rb +4 -0
  96. data/test/root/unload/older.rb +2 -0
  97. data/test/root/unload/unload_base.rb +9 -0
  98. data/test/root/versions/another.yml +0 -0
  99. data/test/root/versions/file-0.1.2.yml +0 -0
  100. data/test/root/versions/file-0.1.yml +0 -0
  101. data/test/root/versions/file.yml +0 -0
  102. data/test/root_test.rb +483 -0
  103. data/test/support/audit_test.rb +449 -0
  104. data/test/support/batch_queue_test.rb +320 -0
  105. data/test/support/combinator_test.rb +249 -0
  106. data/test/support/logger_test.rb +31 -0
  107. data/test/support/template_test.rb +122 -0
  108. data/test/support/templater/erb.txt +2 -0
  109. data/test/support/templater/erb.yml +2 -0
  110. data/test/support/templater/somefile.txt +2 -0
  111. data/test/support/templater_test.rb +192 -0
  112. data/test/support/versions_test.rb +71 -0
  113. data/test/tap_test_helper.rb +4 -0
  114. data/test/tap_test_suite.rb +4 -0
  115. data/test/task/config/batch.yml +2 -0
  116. data/test/task/config/batched.yml +2 -0
  117. data/test/task/config/configured.yml +1 -0
  118. data/test/task/config/example.yml +1 -0
  119. data/test/task/config/overriding.yml +2 -0
  120. data/test/task/config/task_with_config.yml +1 -0
  121. data/test/task/config/template.yml +4 -0
  122. data/test/task_class_test.rb +118 -0
  123. data/test/task_execute_test.rb +233 -0
  124. data/test/task_test.rb +424 -0
  125. data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
  126. data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
  127. data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
  128. data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
  129. data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
  130. data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
  131. data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
  132. data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
  133. data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
  134. data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
  135. data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
  136. data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
  137. data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
  138. data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
  139. data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
  140. data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
  141. data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
  142. data/test/test/inference_methods_test.rb +311 -0
  143. data/test/test/subset_methods_test.rb +115 -0
  144. data/test/test_test.rb +233 -0
  145. data/test/workflow_test.rb +108 -0
  146. 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
@@ -0,0 +1,3 @@
1
+ module Tap
2
+ VERSION="0.7.9"
3
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ factor0: 0
2
+
3
+ variations!:
4
+ - factor1: 10
5
+ - factor0: 1
6
+ factor1: 20
@@ -0,0 +1,2 @@
1
+ - key: one
2
+ - key: two
File without changes
@@ -0,0 +1 @@
1
+ sum: <%= 1 + 2 %>
@@ -0,0 +1,6 @@
1
+ default_field: default value
2
+
3
+ variations!:
4
+ - field: item one
5
+ - field: item two
6
+ default_field: non-default value
@@ -0,0 +1 @@
1
+ version: 0.1
@@ -0,0 +1 @@
1
+ version: empty
@@ -0,0 +1,2 @@
1
+ class AppTestTask < Tap::Task
2
+ end
@@ -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