tap 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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