tap 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. data/Basic Overview +151 -0
  2. data/Command Reference +99 -0
  3. data/History +24 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README +29 -57
  6. data/Rakefile +30 -37
  7. data/Tutorial +243 -191
  8. data/bin/tap +66 -35
  9. data/lib/tap.rb +47 -29
  10. data/lib/tap/app.rb +700 -342
  11. data/lib/tap/{script → cmd}/console.rb +0 -0
  12. data/lib/tap/{script → cmd}/destroy.rb +0 -0
  13. data/lib/tap/{script → cmd}/generate.rb +0 -0
  14. data/lib/tap/cmd/run.rb +156 -0
  15. data/lib/tap/constants.rb +4 -0
  16. data/lib/tap/dump.rb +57 -0
  17. data/lib/tap/env.rb +316 -0
  18. data/lib/tap/file_task.rb +106 -109
  19. data/lib/tap/generator.rb +4 -1
  20. data/lib/tap/generator/generators/command/USAGE +6 -0
  21. data/lib/tap/generator/generators/command/command_generator.rb +17 -0
  22. data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
  23. data/lib/tap/generator/generators/config/USAGE +21 -0
  24. data/lib/tap/generator/generators/config/config_generator.rb +17 -7
  25. data/lib/tap/generator/generators/file_task/USAGE +3 -0
  26. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
  27. data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
  28. data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
  29. data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
  30. data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
  31. data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
  32. data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
  33. data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
  34. data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
  36. data/lib/tap/generator/generators/task/USAGE +3 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +18 -5
  38. data/lib/tap/generator/generators/task/templates/task.erb +7 -12
  39. data/lib/tap/generator/generators/task/templates/test.erb +10 -11
  40. data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
  41. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  42. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  43. data/lib/tap/patches/rake/testtask.rb +55 -0
  44. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  45. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  46. data/lib/tap/root.rb +172 -67
  47. data/lib/tap/script.rb +70 -336
  48. data/lib/tap/support/aggregator.rb +55 -0
  49. data/lib/tap/support/audit.rb +281 -280
  50. data/lib/tap/support/batchable.rb +59 -0
  51. data/lib/tap/support/class_configuration.rb +279 -0
  52. data/lib/tap/support/configurable.rb +92 -0
  53. data/lib/tap/support/configurable_methods.rb +296 -0
  54. data/lib/tap/support/executable.rb +98 -0
  55. data/lib/tap/support/executable_queue.rb +82 -0
  56. data/lib/tap/support/logger.rb +9 -15
  57. data/lib/tap/support/rake.rb +43 -54
  58. data/lib/tap/support/run_error.rb +32 -13
  59. data/lib/tap/support/shell_utils.rb +47 -0
  60. data/lib/tap/support/tdoc.rb +9 -8
  61. data/lib/tap/support/tdoc/config_attr.rb +40 -16
  62. data/lib/tap/support/validation.rb +77 -0
  63. data/lib/tap/support/versions.rb +36 -36
  64. data/lib/tap/task.rb +276 -482
  65. data/lib/tap/test.rb +20 -261
  66. data/lib/tap/test/env_vars.rb +7 -5
  67. data/lib/tap/test/file_methods.rb +126 -121
  68. data/lib/tap/test/subset_methods.rb +86 -45
  69. data/lib/tap/test/tap_methods.rb +271 -0
  70. data/lib/tap/workflow.rb +174 -46
  71. data/test/app/config/another/task.yml +1 -0
  72. data/test/app/config/erb.yml +2 -1
  73. data/test/app/config/some/task.yml +1 -0
  74. data/test/app/config/template.yml +2 -6
  75. data/test/app_test.rb +1241 -1008
  76. data/test/env/test_configure/recurse_a.yml +2 -0
  77. data/test/env/test_configure/recurse_b.yml +2 -0
  78. data/test/env/test_configure/tap.yml +23 -0
  79. data/test/env/test_load_env_config/dir/tap.yml +3 -0
  80. data/test/env/test_load_env_config/recurse_a.yml +2 -0
  81. data/test/env/test_load_env_config/recurse_b.yml +2 -0
  82. data/test/env/test_load_env_config/tap.yml +3 -0
  83. data/test/env_test.rb +198 -0
  84. data/test/file_task_test.rb +70 -53
  85. data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
  86. data/test/root_test.rb +621 -454
  87. data/test/script_test.rb +38 -174
  88. data/test/support/aggregator_test.rb +99 -0
  89. data/test/support/audit_test.rb +409 -416
  90. data/test/support/batchable_test.rb +74 -0
  91. data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
  92. data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
  93. data/test/support/configurable_test.rb +295 -0
  94. data/test/support/executable_queue_test.rb +103 -0
  95. data/test/support/executable_test.rb +38 -0
  96. data/test/support/logger_test.rb +17 -17
  97. data/test/support/rake_test.rb +4 -2
  98. data/test/support/shell_utils_test.rb +24 -0
  99. data/test/support/tdoc_test.rb +265 -258
  100. data/test/support/validation_test.rb +54 -0
  101. data/test/support/versions_test.rb +38 -38
  102. data/test/tap_test_helper.rb +19 -5
  103. data/test/tap_test_suite.rb +5 -2
  104. data/test/task_base_test.rb +13 -104
  105. data/test/task_syntax_test.rb +300 -0
  106. data/test/task_test.rb +258 -381
  107. data/test/test/env_vars_test.rb +40 -40
  108. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
  109. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
  110. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
  111. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
  112. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
  113. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
  114. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
  115. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
  116. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
  117. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
  118. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
  119. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
  120. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
  121. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
  122. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
  123. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
  124. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
  125. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
  126. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
  127. data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
  128. data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
  129. data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
  130. data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
  131. data/test/test/file_methods_doc_test.rb +29 -0
  132. data/test/test/file_methods_test.rb +214 -143
  133. data/test/test/subset_methods_test.rb +111 -115
  134. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
  135. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
  136. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
  137. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
  138. data/test/test/tap_methods_test.rb +399 -0
  139. data/test/workflow_test.rb +101 -91
  140. metadata +86 -70
  141. data/lib/tap/generator/generators/package/package_generator.rb +0 -38
  142. data/lib/tap/generator/generators/package/templates/package.erb +0 -186
  143. data/lib/tap/generator/generators/script/USAGE +0 -0
  144. data/lib/tap/generator/generators/script/script_generator.rb +0 -17
  145. data/lib/tap/script/run.rb +0 -154
  146. data/lib/tap/support/batch_queue.rb +0 -162
  147. data/lib/tap/support/combinator.rb +0 -114
  148. data/lib/tap/support/task_configuration.rb +0 -169
  149. data/lib/tap/support/template.rb +0 -81
  150. data/lib/tap/support/templater.rb +0 -155
  151. data/lib/tap/version.rb +0 -4
  152. data/test/app/config/addition_template.yml +0 -6
  153. data/test/app_class_test.rb +0 -33
  154. data/test/check/binding_eval.rb +0 -23
  155. data/test/check/define_method_check.rb +0 -22
  156. data/test/check/dependencies_check.rb +0 -175
  157. data/test/check/inheritance_check.rb +0 -22
  158. data/test/support/batch_queue_test.rb +0 -320
  159. data/test/support/combinator_test.rb +0 -249
  160. data/test/support/template_test.rb +0 -122
  161. data/test/support/templater/erb.txt +0 -2
  162. data/test/support/templater/erb.yml +0 -2
  163. data/test/support/templater/somefile.txt +0 -2
  164. data/test/support/templater_test.rb +0 -192
  165. data/test/task/config/template.yml +0 -4
  166. data/test/task_class_test.rb +0 -170
  167. data/test/task_execute_test.rb +0 -262
  168. data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
  169. data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
  170. data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
  171. data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
  172. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  173. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  174. data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
  175. data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
  176. data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
  177. data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
  178. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  179. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  180. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  181. data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
  182. data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
  183. data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
  184. data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
  185. data/test/test_test.rb +0 -373
@@ -1,275 +1,34 @@
1
- require 'test/unit'
2
- require 'tap/test/file_methods'
3
- require 'tap/test/subset_methods'
1
+ require 'tap/test/tap_methods'
4
2
 
5
3
  module Test # :nodoc:
6
4
  module Unit # :nodoc:
5
+
6
+ # Methods extending TestCase.
7
+ #
8
+ # === Method Availability
9
+ # Note that these methods are added piecemeal by Tap::Test::SubsetMethods,
10
+ # Tap::Test::FileMethods and Tap::Test::TapMethods, but that fact doesn't
11
+ # come through in the documentation. Hence, not all of them will be available
12
+ # if you're only using SubsetMethods or FileMethods. Breaks down like this:
13
+ #
14
+ # Using: Methods Available:
15
+ # TapMethods all
16
+ # FileMethods all, except acts_as_tap_test
17
+ # SubsetMethods all, except acts_as_tap_test, acts_as_file_test, file_test_root
18
+ #
19
+ #--
20
+ #See the TestTutorial for more information.
7
21
  class TestCase
8
- class << self
9
- # Causes a unit test to act as a tap test -- resulting in the following:
10
- # - setup using acts_as_file_test
11
- # - inclusion of Tap::Test::SubsetMethods
12
- # - inclusion of Tap::Test::InstanceMethods
13
- #
14
- # Note: Unless otherwise specified, +acts_as_tap_test+ infers a root directory
15
- # based on the calling file. Be sure to specify the root directory explicitly
16
- # if you call acts_as_file_test from a file that is NOT meant to be test file.
17
- def acts_as_tap_test(options={})
18
- options = {:root => file_test_root}.merge(options.symbolize_keys)
19
- acts_as_file_test(options)
20
-
21
- include Tap::Test::SubsetMethods
22
- include Tap::Test::InstanceMethods
23
- end
24
-
25
- end
26
22
  end
27
23
  end
28
24
  end
29
25
 
30
26
  module Tap
31
27
 
32
- # == UNDER CONSTRUCTION
28
+ # Modules facilitating testing. Tap::Test::TapMethods are specific to
29
+ # Tap, but the other modules Tap::Test::SubsetMethods and
30
+ # Tap::Test::FileMethods are more general in their utility.
33
31
  module Test
34
-
35
- # Used during check_audit to hold the sources and values of an audit
36
- # in the correct order. Oriented so that the next value to be checked
37
- # is at the top of the stack.
38
- class AuditStack # :nodoc:
39
- attr_reader :test
40
-
41
- def initialize(test)
42
- @test = test
43
- @stack = []
44
- end
45
-
46
- def load_audit(values)
47
- [values._sources, values._values].transpose.reverse_each do |sv|
48
- load(*sv)
49
- end
50
- end
51
-
52
- def load(source, value)
53
- @stack.unshift [source, value]
54
- end
55
-
56
- def next
57
- @stack.shift
58
- end
59
- end
60
-
61
- module InstanceMethods # :nodoc:
62
-
63
- # Setup clears the test using clear_tasks and assures that Tap::App.instance
64
- # is the test-specific application.
65
- def setup
66
- super
67
- Tap::App.instance = app
68
- app.queue.clear
69
- end
70
-
71
- # Returns the test-specific application.
72
- def app
73
- @app ||= Tap::App.new(:root => trs.root, :directories => trs.directories)
74
- end
75
-
76
- # Recieves a hash of task => expected pairs. Asserts that the last inputs for
77
- # each task are equal to the expected inputs.
78
- def assert_inputs(hash)
79
- hash.each_pair do |task, expected|
80
- inputs = task.results.collect { |r| r._input_last(task) }
81
- assert_equal expected, inputs
82
- end
83
- end
84
-
85
- # Recieves a hash of results => [index, expected] pairs. Asserts that the task result
86
- # inputs at the specified index are equal to the expected inputs.
87
- def assert_inputs_by_index(results, hash)
88
- hash.each_pair do |index, expected|
89
- assert_equal expected, results.collect {|r| r._input(index)}
90
- end
91
- end
92
-
93
- # Recieves a hash of task => expected pairs. Asserts that the last outputs for
94
- # each task are equal to the expected outputs.
95
- def assert_outputs(hash)
96
- hash.each_pair do |task, expected|
97
- outputs = task.results.collect { |r| r._output_last(task) }
98
- assert_equal expected, outputs
99
- end
100
- end
101
-
102
- # Recieves a hash of results => [index, expected] pairs. Asserts that the result
103
- # outputs at the specified index are equal to the expected outputs.
104
- def assert_outputs_by_index(results, hash)
105
- hash.each_pair do |index, expected|
106
- assert_equal expected, results.collect {|r| r._output(index)}
107
- end
108
- end
109
-
110
- # Recieves an array of audits and a hash of [index, expected_audit] pairs. Asserts that
111
- # the collection of all [source, value] pairs in the indexed audit matches the corresponding
112
- # pairs provided in expected_audit.
113
- #
114
- # Example:
115
- # a0 = Audit.new('')
116
- # a1 = Audit.new('')
117
- # a1.record(:a, 'a')
118
- # a1.record(:b, 'b')
119
- #
120
- # assert_audits([a0, a1], 1 => [[nil, ''], [:a, 'a'], [:b, 'b']]) # => true
121
- #
122
- # Note that since task results are an array of audits:
123
- # t.results # if => [a0, a1]
124
- # assert_audits(t1.results, 1 => [[nil, ''], [:a, 'a'], [:b, 'b']]) # then => true
125
- #
126
- def assert_audits(audits, hash)
127
- hash.each_pair do |i, expected|
128
- audit = audits[i]
129
- raise ArgumentError.new("No audit provided at index: #{i}") if audit.nil?
130
-
131
- actual = [collect_object_ids(audit._source_trail), audit._values].transpose
132
- expected = expected.transpose
133
- expected[0] = collect_object_ids(expected[0])
134
- expected = expected.transpose
135
- assert_equal expected, actual, "Unexpected audit for result #{i}.\nAudits displayed as: [[source.object_id, value]]"
136
- end
137
- end
138
-
139
- def check_audit(audit, &block)
140
- raise "Cannot check audit. Public method ':_from' already defined for Object." if Object.public_method_defined?(:_from)
141
-
142
- begin
143
- audit_stack = AuditStack.new(self)
144
- audit_stack.load_audit(audit)
145
-
146
- # Define the :from testing method. Defining this method on Object is good for syntax,
147
- # but generally a bad practice. Hence, the method is undefined immediately after the
148
- # test block completes.
149
- #
150
- # The from method assumes the object itself is the expected value, and that the expected
151
- # source is given as an argument. If a block is given to from, that indicates that the value
152
- # is the result of a merge -- the block must contain the checks to assert the origins of the
153
- # merge value.
154
- Object.class_eval %Q{
155
- def _from(expected_source=nil, &block)
156
- audit_stack = ObjectSpace._id2ref(#{audit_stack.object_id})
157
- source, value = audit_stack.next
158
-
159
- if block_given?
160
- audit_stack.test.assert_equal Array, source.class, "Expected source from merge."
161
- [source, value].transpose.reverse_each do |source, value|
162
- source.kind_of?(Tap::Support::Audit) ?
163
- audit_stack.load_audit(source) :
164
- audit_stack.load(source, value)
165
- end
166
- yield
167
- source, value = audit_stack.next
168
- end
169
-
170
- audit_stack.test.assert_equal self, value, "Wrong audit value (be sure your checks are in the correct order)"
171
- audit_stack.test.assert_equal expected_source.object_id, source.object_id, PP.singleline_pp(value, "Wrong source id for value: ")
172
- end}
173
-
174
- yield
175
- ensure
176
- Object.class_eval %Q{remove_method(:_from)} if Object.public_method_defined?(:_from)
177
- end
178
- end
179
-
180
- # Applies the input options to the specified app for the duration
181
- # of the block. Unless merge_with_existing is false, the input
182
- # options will be merged with the existing options; otherwise
183
- # the app options will be reconfigured to just the inputs.
184
- #
185
- # app = Tap::App.new(:options => {:one => 1, :two => 2})
186
- #
187
- # with_options({:one => 'one'}, app) do
188
- # app.options.marshal_dump # => {:one => 'one', :two => 2}
189
- # end
190
- # app.options.marshal_dump # => {:one => 1, :two => 2}
191
- #
192
- def with_options(options, app=self.app, merge_with_existing=true, &block)
193
- app_config = {:options => options}
194
- with_config(app_config, app, merge_with_existing, &block)
195
- end
196
-
197
- # Applies the input configurations to the specified app for the
198
- # duration of the block. Unless merge_with_existing is false,
199
- # the input configurations will be merged with the existing
200
- # configurations; otherwise the app will be reconfigured to
201
- # using the inputs as specified.
202
- #
203
- # app = Tap::App.new(:directories => {:dir => 'dir', :alt => 'alt_dir'})
204
- # tmp_config = {
205
- # :directories => {:alt => 'another', :new => 'new_dir'},
206
- # :options => {:one => 1}}
207
- #
208
- # with_config(tmp_config, app) do
209
- # app.directories # => {:dir => 'dir', :alt => 'another', :new => 'new_dir'}
210
- # app.options.marshal_dump # => {:one => 1}
211
- # end
212
- # app.directories # => {:dir => 'dir', :alt => 'alt_dir'}
213
- # app.options.marshal_dump # => {}
214
- #
215
- def with_config(app_config, app=self.app, merge_with_existing=true, &block)
216
- begin
217
- hold = app.config
218
- if merge_with_existing
219
- hold.each_pair do |key, value|
220
- next unless app_config.has_key?(key)
221
- next unless value.kind_of?(Hash)
222
-
223
- app_config[key] = value.merge(app_config[key])
224
- end
225
- end
226
- app.reconfigure(app_config)
227
-
228
- yield block if block_given?
229
- ensure
230
- app.reconfigure(hold)
231
- end
232
- end
233
-
234
- # assert_expected_result_files runs the input task using the
235
- # input app config, submitting all method input files for
236
- # processing. the assertions returns true only if all the
237
- # expected and output files are equal. As such it is very
238
- # convenient for file transform tasks.
239
- #
240
- # The default application config sets the app root to
241
- # method_root, silences execution and directs app[:data] to
242
- # the method output directory.
243
- #
244
- # assert_expected_result_files makes the input/expected
245
- # directories if they do not exist. By default all the
246
- # output files will be cleaned up during teardown. See
247
- # FileMethods for more information.
248
- def assert_expected_result_files(task, app_config={})
249
- make_test_directories
250
-
251
- app_config = {
252
- :root => method_root,
253
- :options => {:quiet => true},
254
- :absolute_paths => {:data => method_filepath(:output)}
255
- }.merge(app_config)
256
-
257
- with_config(app_config, task.app) do
258
- assert_output_files_equal do |input_files|
259
- output_files = task.execute(*input_files).collect {|a| a._current}
260
- block_given? ? yield(output_files) : output_files
261
- end
262
- end
263
- end
264
-
265
- private
266
-
267
- def collect_object_ids(array)
268
- array.collect do |s|
269
- s.kind_of?(Array) ? collect_object_ids(s) : s.object_id
270
- end
271
- end
272
- end
273
32
  end
274
33
  end
275
34
 
@@ -8,7 +8,9 @@ module Tap
8
8
  # if multiple case-insensitive values are defined in ENV.
9
9
  def env(type)
10
10
  type = type.downcase
11
- selected = ENV.select {|key, value| key.downcase == type}
11
+
12
+ # ruby 1.9 returns a hash instead of an array
13
+ selected = ENV.select {|key, value| key.downcase == type}.to_a
12
14
 
13
15
  case selected.length
14
16
  when 0 then nil
@@ -17,10 +19,10 @@ module Tap
17
19
  raise "Multiple env values for '#{type}'"
18
20
  end
19
21
  end
20
-
21
- # Returns true if the env_var(var) is set and matches /^true%/i
22
- def env_true?(var)
23
- env(var) && env(var) =~ /^true$/i
22
+
23
+ # Returns true if the env_var(var) is set and matches /^true%/i
24
+ def env_true?(var)
25
+ env(var) && env(var) =~ /^true$/i
24
26
  end
25
27
  end
26
28
  end
@@ -2,32 +2,29 @@ require 'tap/root'
2
2
  require 'tap/test/env_vars'
3
3
  require 'test/unit'
4
4
  require 'fileutils'
5
+ require 'active_support/core_ext/class'
5
6
 
6
7
  module Test # :nodoc:
7
8
  module Unit # :nodoc:
8
- # Methods extending TestCase. See the TestTutorial for more information.
9
9
  class TestCase
10
10
  class << self
11
11
 
12
12
  # Causes a TestCase to act as a file test, by instantiating a class Tap::Root
13
- # (trs), and including the FileMethods. The root and directories used to
13
+ # (trs), and including FileMethods. The root and directories used to
14
14
  # instantiate trs can be specified as options. By default file_test_root
15
- # and default_directories will be used.
15
+ # and the directories {:input => 'input', :output => 'output', :expected => 'expected'}
16
+ # will be used.
16
17
  #
17
- # class FileDependentTest < Test::Unit::TestCase
18
- # acts_as_file_test(:root => file_test_root, :directories => default_directories)
19
- # end
20
- #
21
- # Note: file_test_root determines a root directory +based on the calling file+.
18
+ # Note: file_test_root determines a root directory <em>based on the calling file</em>.
22
19
  # Be sure to specify the root directory explicitly if you call acts_as_file_test
23
20
  # from a file that is NOT meant to be test file.
24
21
  def acts_as_file_test(options={})
25
22
  options = {
26
23
  :root => file_test_root,
27
- :directories => {}
28
- }.merge(options.symbolize_keys)
24
+ :directories => {:input => 'input', :output => 'output', :expected => 'expected'}
25
+ }.merge(options)
29
26
 
30
- directories = default_directories.merge(options[:directories])
27
+ directories = options[:directories]
31
28
  trs = Tap::Root.new(options[:root], directories)
32
29
 
33
30
  write_inheritable_attribute(:trs, trs)
@@ -35,15 +32,6 @@ module Test # :nodoc:
35
32
 
36
33
  include Tap::Test::FileMethods
37
34
  end
38
-
39
- # The default directories for a file test:
40
- #
41
- # :input => 'input'
42
- # :output => 'output'
43
- # :expected => 'expected'
44
- def default_directories
45
- {:input => 'input', :output => 'output', :expected => 'expected'}
46
- end
47
35
 
48
36
  # Infers the test root directory from the calling file. Ex:
49
37
  # 'some_class.rb' => 'some_class'
@@ -69,80 +57,84 @@ end
69
57
  module Tap
70
58
  module Test
71
59
 
72
- # == Under Construction
60
+ # == Overview
73
61
  #
74
62
  # FileMethods sets up a TestCase with methods for accessing and utilizing
75
- # test-specific files and directories. Each class including FileMethods
63
+ # test-specific files and directories. Each class that acts_as_file_test
76
64
  # is set up with a Tap::Root structure (trs) that mediates the creation of
77
- # test method filepaths. FileMethods extends TestCase with the
78
- # acts_as_file_test to include the FileMethods and initialize the trs.
65
+ # test method filepaths.
79
66
  #
80
- # class FileDependentTest < Test::Unit::TestCase
67
+ # class FileMethodsDocTest < Test::Unit::TestCase
81
68
  # acts_as_file_test
82
- #
83
- # def test_file_transform
84
- # trs.root # => "."
85
- # method_root # => "./file_dependent_test/test_file_transform"
86
- # method_dir(:input) # => "./file_dependent_test/test_file_transform/input"
69
+ #
70
+ # def test_something
71
+ # # dir = File.expand_path( File.dirname(__FILE__) )
72
+ # trs.root # => dir + "/file_methods_doc",
73
+ # method_root # => dir + "/file_methods_doc/test_something",
74
+ # method_dir(:input) # => dir + "/file_methods_doc/test_something/input"
87
75
  # end
88
76
  # end
89
77
  #
90
- # == File Transform Tests
78
+ # === assert_files
91
79
  #
92
80
  # FileMethods is specifically designed for tests that transform a set of input
93
81
  # files into output files. For this type of test, input and expected files can
94
82
  # placed into their respective directories then used within the context of
95
- # assert_output_files_equal to ensure the output files are equal to the expected
96
- # files.
83
+ # assert_files to ensure the output files are equal to the expected files.
97
84
  #
98
85
  # For example, lets define a test that transforms input files into output files
99
86
  # in a trivial way, simply by replacing 'input' with 'output' in the file.
100
87
  #
101
- # class FileTest < Test::Unit::TestCase
88
+ # class FileMethodsDocTest < Test::Unit::TestCase
102
89
  # acts_as_file_test
103
- #
104
- # def test_transform
105
- # assert_output_files_equal do |input_files|
90
+ #
91
+ # def test_sub
92
+ # assert_files do |input_files|
106
93
  # input_files.collect do |filepath|
107
94
  # input = File.read(filepath)
108
- # output_file = method_filepath(:ouput_files, File.basename(filepath))
95
+ # output_file = method_filepath(:output, File.basename(filepath))
109
96
  #
110
97
  # File.open(output_file, "w") do |f|
111
98
  # f << input.gsub(/input/, "output")
112
99
  # end
100
+ #
101
+ # output_file
113
102
  # end
114
103
  # end
115
104
  # end
116
105
  # end
117
106
  #
118
- # Now make some input and expected files for the 'test_transform' method:
107
+ # Now say you had some input and expected files for the 'test_sub' method:
119
108
  #
120
- # FileUtils.mkdir_p("file_test/test_transform/input_files")
121
- # FileUtils.mkdir_p("file_test/test_transform/expected_files")
122
- #
123
- # [file_one.txt", file_two.txt"].each_with_index do |basename, i|
124
- # input = "file_test/test_transform/input_files/" + basename
125
- # File.open(input, "w") {|f| f << "test input #{i}"}
109
+ # [file_methods_doc/test_sub/input/one.txt]
110
+ # test input 1
126
111
  #
127
- # expected = "file_test/test_transform/expected_files/" + basename
128
- # File.open(expected, "w") {|f| f << "test output #{i}"}
129
- # end
112
+ # [file_methods_doc/test_sub/input/two.txt]
113
+ # test input 2
114
+ #
115
+ # [file_methods_doc/test_sub/expected/one.txt]
116
+ # test output 1
130
117
  #
131
- # When you run the FileTest tests, the test_transform test will pass
132
- # the existing input_files to the assert_output_files_equal block. The
133
- # block makes output files in "file_test/test_transform/output_files",
134
- # and then assert_output_files_equal compares the files and finds they
135
- # are equal. As a result the test passes.
118
+ # [file_methods_doc/test_sub/expected/two.txt]
119
+ # test output 2
136
120
  #
137
- # The test fails if you alter the expected file contents, add/remove
138
- # expected files, or in some other way make these files unequal.
121
+ # When you run the FileMethodsDocTest test, the test_sub test will pass
122
+ # the input files to the assert_files block. Then assert_files compares
123
+ # the returned filepaths with the expected files translated from the
124
+ # expected directory to the output directory. In this case, the files
125
+ # are equal and the test passes.
139
126
  #
140
- # When the test completes, the default teardown method will clean up the
141
- # output_files directory. For ease in debugging, ENV variable flags can
142
- # be specified to keep all output files (KEEP_OUTPUTS) or to keep the
143
- # output files for just the tests that fail (KEEP_FAILURES). These flags
144
- # can easily be specified from the command line when running a rake task:
127
+ # The test fails if the returned files aren't equal to the expected files,
128
+ # either because there are missing or extra files, or if the file contents
129
+ # are different.
145
130
  #
131
+ # When the test completes, the teardown method cleans up the output directory.
132
+ # For ease in debugging, ENV variable flags can be specified to keep all
133
+ # output files (KEEP_OUTPUTS) or to keep the output files for just the tests
134
+ # that fail (KEEP_FAILURES). These flags can be specified from the command
135
+ # line if you're running the tests with rake or tap:
136
+ #
137
+ # % rake test keep_outputs=true
146
138
  # % tap run test keep_failures=true
147
139
  #
148
140
  module FileMethods
@@ -156,7 +148,7 @@ module Tap
156
148
  # Creates the trs.directories, specific to the method calling make_test_directories
157
149
  def make_test_directories
158
150
  trs.directories.values.each do |dir|
159
- FileUtils.mkdir_p( File.join(trs.root, method_name, dir) )
151
+ FileUtils.mkdir_p( File.join(trs.root, method_name_str, dir) )
160
152
  end
161
153
  end
162
154
 
@@ -188,16 +180,21 @@ module Tap
188
180
  try_remove_dir(method_root)
189
181
  try_remove_dir(trs.root)
190
182
  end
183
+
184
+ # Returns method_name as a string (Ruby 1.9 symbolizes method_name)
185
+ def method_name_str
186
+ method_name.to_s
187
+ end
191
188
 
192
189
  # The method_root directory is defined as trs.filepath(method_name)
193
- def method_root(method=method_name)
190
+ def method_root(method=method_name_str)
194
191
  trs.filepath(method)
195
192
  end
196
193
 
197
194
  # The method directory is defined as 'dir/method', where method is the calling method
198
195
  # by default. method_dir returns the method directory if it exists, otherwise it returns
199
196
  # trs[dir].
200
- def method_dir(dir, method=method_name)
197
+ def method_dir(dir, method=method_name_str)
201
198
  File.join(method_root(method), trs.directories[dir] || dir.to_s)
202
199
  end
203
200
 
@@ -209,7 +206,7 @@ module Tap
209
206
  end
210
207
 
211
208
  # Returns a filepath constructed from the method directory if it exists,
212
- # otherwise the filepath will be constructed from <tt>trs[dir]</tt>.
209
+ # otherwise the filepath will be constructed from <tt>trs[dir]</tt>.
213
210
  def method_filepath(dir, *filenames)
214
211
  File.join(method_dir(dir), *filenames)
215
212
  end
@@ -236,7 +233,7 @@ module Tap
236
233
  # files within it. Raises an error if the removal does not succeed.
237
234
  def clear_method_dir(dir)
238
235
  # clear out the folder if it exists
239
- dir_path = method_dir(dir, method_name)
236
+ dir_path = method_dir(dir, method_name_str)
240
237
  FileUtils.rm_r(dir_path) if File.exists?(dir_path)
241
238
  end
242
239
 
@@ -260,8 +257,8 @@ module Tap
260
257
  # - The extension is chomped off the end of the filename
261
258
  # - If the directory for the filepath does not exist, the directory will be created
262
259
  # - Like all files in the output directory, tempfiles will be deleted by the default
263
- # +teardown+ method
264
- def tempfile(filename=method_name)
260
+ # +teardown+ method
261
+ def output_tempfile(filename=method_name_str)
265
262
  n = 0
266
263
  ext = File.extname(filename)
267
264
  basename = filename.chomp(ext)
@@ -275,88 +272,96 @@ module Tap
275
272
  FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
276
273
  filepath
277
274
  end
278
-
279
- # Asserts that each pair of input files are equal, using FileUtils.cmp.
280
- def files_equal?(a, b)
281
- FileUtils.cmp(a, b)
282
- end
283
-
284
- # Asserts that each pair of input files produce an equivalent object when
285
- # loaded as a yaml file.
286
- def yml_equal?(a, b)
287
- YAML.load_file(a) == YAML.load_file(b)
288
- end
289
275
 
290
- # Yields to the input block for each pair of input files. An error is raised
291
- # if the input arrays do not have equal numbers of entries.
292
- def each_pair(a, b, &block)
276
+ # Yields to the input block for each pair of entries in the input
277
+ # arrays. An error is raised if the input arrays do not have equal
278
+ # numbers of entries.
279
+ def each_pair(a, b, &block) # :yields: entry_a, entry_b,
280
+ each_pair_with_index(a,b) do |entry_a, entry_b, index|
281
+ yield(entry_a, entry_b)
282
+ end
283
+ end
284
+
285
+ # Same as each_pair but yields the index of the entries as well.
286
+ def each_pair_with_index(a, b, &block) # :yields: entry_a, entry_b, index
293
287
  a = [a] unless a.kind_of?(Array)
294
288
  b = [b] unless b.kind_of?(Array)
295
289
 
296
290
  raise ArgumentError, "The input arrays must have an equal number of entries." unless a.length == b.length
297
- a.each_index do |index|
298
- yield(a[index], b[index])
291
+ 0.upto(a.length-1) do |index|
292
+ yield(a[index], b[index], index)
299
293
  end
300
294
  end
301
295
 
302
- def assert_output_files_equal(options={}) # :yields: input_files
296
+ # assert_files runs a file-based test that feeds all files in method_dir(:input)
297
+ # to the block, then compares the resulting files (which should be relative to
298
+ # method_dir(:output)) with all the files in method_dir(:expected). Note that
299
+ # since only the files returned by the block are used in the comparison,
300
+ # additional files in the output directory are effectively ignored.
301
+ #
302
+ # A variety of options can be specified to adjust the behavior:
303
+ #
304
+ # :input_files specify the input files to pass to the block
305
+ # :expected_files specify the expected files used in comparison
306
+ # :include_input_directories specifies directories to be included in the
307
+ # input_files array (by default dirs are excluded)
308
+ # :include_expected_directories specifies directories to be included in the
309
+ # expected-output file list comparison (by default
310
+ # dirs are excluded, note that naturally only files
311
+ # have their actual content compared)
312
+ #
313
+ # Option keys should be symbols. assert_files will fail if :expected_files was
314
+ # not specified in the options and no files were found in method_dir(:expected).
315
+ # This tries to prevent silent false-positive results when you forget to put
316
+ # expected files in their place.
317
+ def assert_files(options={}) # :yields: input_files
303
318
  make_test_directories
304
319
 
305
320
  options = {
306
321
  :input_files => nil,
307
322
  :expected_files => nil,
308
- :require_expected_files => true,
309
- :check_for_missing_outputs => true,
310
- :iterate_inputs => false,
311
- :include_directories => false
323
+
324
+ :include_input_directories => false,
325
+ :include_expected_directories => false
312
326
  }.merge(options)
313
327
 
314
- # set or search for input and expected files, and arrayify
328
+ # Get the input and expected files in this manner:
329
+ # - look for manually specified files
330
+ # - glob for files if none were specified
331
+ # - expand paths and sort
332
+ # - remove directories unless specified not to do so
315
333
  input_files, expected_files = [:input, :expected].collect do |key|
316
- options_key = "#{key}_files".to_sym
317
-
318
- files = options.delete(options_key)
334
+ files = options["#{key}_files".to_sym]
319
335
  files = method_glob(key) if files.nil?
320
- files = (files.kind_of?(Array) ? files : [files])
321
- files.collect! {|file| File.expand_path(file) }
322
- unless options[:include_directories]
336
+ files = [files].flatten.collect {|file| File.expand_path(file) }.sort
337
+
338
+ unless options["include_#{key}_directories".to_sym]
323
339
  files.delete_if {|file| File.directory?(file)}
324
340
  end
341
+
325
342
  files
326
343
  end
327
-
328
- flunk "No expected files were specfied." if options[:require_expected_files] && expected_files.empty?
329
-
330
- # create output files
331
- output_files = if options[:iterate_inputs]
332
- input_files.collect do |input_file|
333
- yield(input_file)
334
- end
335
- else
336
- yield(input_files)
337
- end
338
- output_files = output_files.flatten.collect {|file| File.expand_path(file) }
339
344
 
340
- if options[:check_for_missing_outputs]
341
- expected_output_files = method_glob(:output)
342
- unless options[:include_directories]
343
- expected_output_files.delete_if {|file| File.directory?(file)}
344
- end
345
- expected_output_files.collect! {|file| File.expand_path(file) }
346
-
347
- # assure there are no missing or extra output files
348
- assert_equal expected_output_files, output_files, "Missing or extra output files"
345
+ # check at least one expected file was found
346
+ if expected_files.empty? && options[:expected_files] == nil
347
+ flunk "No expected files specified."
349
348
  end
349
+
350
+ # get output files from the block, expand and sort
351
+ output_files = [yield(input_files)].flatten.collect do |output_file|
352
+ output_file = File.expand_path(output_file)
353
+ end.sort
350
354
 
351
355
  # check that the expected and output filepaths are the same
352
- translated_files = output_files.collect {|file| method_translate(file, :output, :expected)}
353
- translated_files.collect! {|file| File.expand_path(file) }
354
- assert_equal expected_files, translated_files, "Missing or extra expected files"
356
+ translated_expected_files = expected_files.collect do |expected_file|
357
+ method_translate(expected_file, :expected, :output)
358
+ end
359
+ assert_equal translated_expected_files, output_files, "Missing, extra, or unexpected output files"
355
360
 
356
361
  # check that the expected and output file contents are equal
357
362
  errors = []
358
363
  each_pair(expected_files, output_files) do |expected_file, output_file|
359
- unless files_equal?(expected_file, output_file)
364
+ unless (File.directory?(expected_file) && File.directory?(output_file)) || FileUtils.cmp(expected_file, output_file)
360
365
  errors << "<#{expected_file}> not equal to\n<#{output_file}>"
361
366
  end
362
367
  end
@@ -365,7 +370,7 @@ module Tap
365
370
 
366
371
  private
367
372
 
368
- def make_tmpname(basename, n, ext="")
373
+ def make_tmpname(basename, n, ext="") # :nodoc:
369
374
  method_filepath(:output, sprintf('%s%d.%d%s', basename, $$, n, ext))
370
375
  end
371
376
  end