tap 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +28 -0
- data/MIT-LICENSE +1 -1
- data/README +71 -43
- data/Rakefile +81 -64
- data/Tutorial +235 -0
- data/bin/tap +80 -44
- data/lib/tap.rb +41 -12
- data/lib/tap/app.rb +243 -246
- data/lib/tap/file_task.rb +357 -118
- data/lib/tap/generator.rb +88 -29
- data/lib/tap/generator/generators/config/config_generator.rb +4 -2
- data/lib/tap/generator/generators/config/templates/config.erb +1 -2
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
- data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
- data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
- data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
- data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
- data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
- data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
- data/lib/tap/generator/generators/package/package_generator.rb +38 -0
- data/lib/tap/generator/generators/package/templates/package.erb +186 -0
- data/lib/tap/generator/generators/root/root_generator.rb +14 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
- data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
- data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +17 -0
- data/lib/tap/generator/generators/script/templates/script.erb +42 -0
- data/lib/tap/generator/generators/task/task_generator.rb +1 -1
- data/lib/tap/generator/generators/task/templates/task.erb +24 -16
- data/lib/tap/generator/generators/task/templates/test.erb +13 -17
- data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
- data/lib/tap/root.rb +108 -146
- data/lib/tap/script.rb +362 -0
- data/lib/tap/script/console.rb +28 -0
- data/lib/tap/script/destroy.rb +13 -1
- data/lib/tap/script/generate.rb +13 -1
- data/lib/tap/script/run.rb +100 -57
- data/lib/tap/support/batch_queue.rb +0 -3
- data/lib/tap/support/logger.rb +6 -3
- data/lib/tap/support/rake.rb +54 -0
- data/lib/tap/support/task_configuration.rb +169 -0
- data/lib/tap/support/tdoc.rb +198 -0
- data/lib/tap/support/tdoc/config_attr.rb +338 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/versions.rb +33 -1
- data/lib/tap/task.rb +339 -227
- data/lib/tap/test.rb +86 -128
- data/lib/tap/test/env_vars.rb +16 -5
- data/lib/tap/test/file_methods.rb +373 -0
- data/lib/tap/test/subset_methods.rb +299 -180
- data/lib/tap/version.rb +2 -1
- data/lib/tap/workflow.rb +2 -0
- data/test/app/lib/app_test_task.rb +1 -0
- data/test/app_test.rb +327 -83
- data/test/check/binding_eval.rb +23 -0
- data/test/check/define_method_check.rb +22 -0
- data/test/check/dependencies_check.rb +175 -0
- data/test/check/inheritance_check.rb +22 -0
- data/test/file_task_test.rb +524 -291
- data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root_test.rb +330 -262
- data/test/script_test.rb +194 -0
- data/test/support/audit_test.rb +5 -2
- data/test/support/combinator_test.rb +10 -10
- data/test/support/rake_test.rb +35 -0
- data/test/support/task_configuration_test.rb +272 -0
- data/test/support/tdoc_test.rb +363 -0
- data/test/support/templater_test.rb +2 -2
- data/test/support/versions_test.rb +32 -0
- data/test/tap_test_helper.rb +39 -0
- data/test/task_base_test.rb +115 -0
- data/test/task_class_test.rb +56 -4
- data/test/task_execute_test.rb +29 -0
- data/test/task_test.rb +89 -70
- data/test/test/env_vars_test.rb +48 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
- data/test/test/file_methods_test.rb +204 -0
- data/test/test/subset_methods_test.rb +93 -33
- data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
- data/test/test/test_file_task_test/expected/one.txt +1 -0
- data/test/test/test_file_task_test/expected/two.txt +1 -0
- data/test/test/test_file_task_test/input/one.txt +1 -0
- data/test/test/test_file_task_test/input/two.txt +1 -0
- data/test/test_test.rb +143 -3
- data/test/workflow_test.rb +2 -0
- data/vendor/rails_generator.rb +56 -0
- data/vendor/rails_generator/base.rb +263 -0
- data/vendor/rails_generator/commands.rb +581 -0
- data/vendor/rails_generator/generated_attribute.rb +42 -0
- data/vendor/rails_generator/lookup.rb +209 -0
- data/vendor/rails_generator/manifest.rb +53 -0
- data/vendor/rails_generator/options.rb +143 -0
- data/vendor/rails_generator/scripts.rb +83 -0
- data/vendor/rails_generator/scripts/destroy.rb +7 -0
- data/vendor/rails_generator/scripts/generate.rb +7 -0
- data/vendor/rails_generator/scripts/update.rb +12 -0
- data/vendor/rails_generator/simple_logger.rb +46 -0
- data/vendor/rails_generator/spec.rb +44 -0
- metadata +180 -196
- data/lib/tap/generator/generators/root/templates/app.yml +0 -19
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
- data/lib/tap/script/server.rb +0 -12
- data/lib/tap/support/rap.rb +0 -38
- data/lib/tap/test/inference_methods.rb +0 -298
- data/test/task/config/task_with_config.yml +0 -1
- data/test/test/inference_methods_test.rb +0 -311
data/lib/tap/test.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
require 'tap/test/
|
2
|
+
require 'tap/test/file_methods'
|
3
3
|
require 'tap/test/subset_methods'
|
4
4
|
|
5
5
|
module Test # :nodoc:
|
@@ -7,21 +7,21 @@ module Test # :nodoc:
|
|
7
7
|
class TestCase
|
8
8
|
class << self
|
9
9
|
# Causes a unit test to act as a tap test -- resulting in the following:
|
10
|
-
# - setup
|
11
|
-
# -
|
12
|
-
#
|
10
|
+
# - setup using acts_as_file_test
|
11
|
+
# - inclusion of Tap::Test::SubsetMethods
|
12
|
+
# - inclusion of Tap::Test::InstanceMethods
|
13
13
|
#
|
14
14
|
# Note: Unless otherwise specified, +acts_as_tap_test+ infers a root directory
|
15
|
-
# based on the calling file.
|
16
|
-
#
|
17
|
-
# explicitly if you call +acts_as_tap_test+ from a file that is NOT your test file.
|
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.
|
18
17
|
def acts_as_tap_test(options={})
|
19
|
-
options = {:root =>
|
20
|
-
|
18
|
+
options = {:root => file_test_root}.merge(options.symbolize_keys)
|
19
|
+
acts_as_file_test(options)
|
21
20
|
|
22
21
|
include Tap::Test::SubsetMethods
|
23
22
|
include Tap::Test::InstanceMethods
|
24
23
|
end
|
24
|
+
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -29,13 +29,13 @@ end
|
|
29
29
|
|
30
30
|
module Tap
|
31
31
|
|
32
|
-
#
|
32
|
+
# == UNDER CONSTRUCTION
|
33
33
|
module Test
|
34
34
|
|
35
35
|
# Used during check_audit to hold the sources and values of an audit
|
36
36
|
# in the correct order. Oriented so that the next value to be checked
|
37
37
|
# is at the top of the stack.
|
38
|
-
class AuditStack
|
38
|
+
class AuditStack # :nodoc:
|
39
39
|
attr_reader :test
|
40
40
|
|
41
41
|
def initialize(test)
|
@@ -58,70 +58,21 @@ module Tap
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
module InstanceMethods
|
62
|
-
attr_accessor :runlist
|
61
|
+
module InstanceMethods # :nodoc:
|
63
62
|
|
64
63
|
# Setup clears the test using clear_tasks and assures that Tap::App.instance
|
65
64
|
# is the test-specific application.
|
66
65
|
def setup
|
67
66
|
super
|
68
67
|
Tap::App.instance = app
|
69
|
-
|
68
|
+
app.queue.clear
|
70
69
|
end
|
71
70
|
|
72
71
|
# Returns the test-specific application.
|
73
72
|
def app
|
74
|
-
@app ||= Tap::App.new(:root =>
|
75
|
-
end
|
76
|
-
|
77
|
-
# Clears all declared tasks, sets the application trace option to false, makes directories (if flagged
|
78
|
-
# and as needed), and clears the runlist.
|
79
|
-
def clear_runlist
|
80
|
-
# clear the attributes
|
81
|
-
@runlist = []
|
82
|
-
end
|
83
|
-
|
84
|
-
# A tracing procedure. echo adds input to runlist then returns input.
|
85
|
-
def echo
|
86
|
-
lambda do |task, input|
|
87
|
-
@runlist << input
|
88
|
-
input
|
89
|
-
end
|
73
|
+
@app ||= Tap::App.new(:root => trs.root, :directories => trs.directories)
|
90
74
|
end
|
91
75
|
|
92
|
-
# A tracing procedure for numeric inputs. add_one adds the input to
|
93
|
-
# runlist then returns input + 1.
|
94
|
-
def add_one
|
95
|
-
lambda do |task, input|
|
96
|
-
@runlist << input
|
97
|
-
input += 1
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# A convenience testing method asserting that runlist array is equal to the inputs
|
102
|
-
# def assert_runlist(*expected)
|
103
|
-
# assert_equal expected, runlist
|
104
|
-
# end
|
105
|
-
|
106
|
-
# A convenience testing method asserting that sorted runlist array is equal to the
|
107
|
-
# sorted inputs. Sorting the runlist is unpreferred, because you lose information about
|
108
|
-
# the order of items added to the runlist.
|
109
|
-
#
|
110
|
-
# However, at times there is no good alternative because the order of execution
|
111
|
-
# may be determined by a hash.
|
112
|
-
# def assert_sorted_runlist(*expected)
|
113
|
-
# two-step in case the elements cannot be sorted (as in the case of hashes)
|
114
|
-
# found_all = (expected.length == runlist.length)
|
115
|
-
# expected.each do |e|
|
116
|
-
# break unless found_all
|
117
|
-
|
118
|
-
# next if runlist.include?(e)
|
119
|
-
# found_all = false
|
120
|
-
# end
|
121
|
-
|
122
|
-
# assert found_all, "Expected: #{PP.pp(expected, '')}Was: #{PP.pp(runlist, '')}"
|
123
|
-
# end
|
124
|
-
|
125
76
|
# Recieves a hash of task => expected pairs. Asserts that the last inputs for
|
126
77
|
# each task are equal to the expected inputs.
|
127
78
|
def assert_inputs(hash)
|
@@ -226,80 +177,87 @@ module Tap
|
|
226
177
|
end
|
227
178
|
end
|
228
179
|
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
|
233
|
-
|
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)
|
234
216
|
begin
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
238
225
|
end
|
239
|
-
|
226
|
+
app.reconfigure(app_config)
|
227
|
+
|
240
228
|
yield block if block_given?
|
241
229
|
ensure
|
242
|
-
|
230
|
+
app.reconfigure(hold)
|
243
231
|
end
|
244
232
|
end
|
245
|
-
|
246
|
-
def file_task_test(task, options={})
|
247
|
-
make_directory_structure
|
248
|
-
|
249
|
-
raise "Not a FileTask" unless task.kind_of?(Tap::FileTask)
|
250
|
-
|
251
|
-
options = {
|
252
|
-
:input_files => nil,
|
253
|
-
:expected_files => nil,
|
254
|
-
:strict => true#,
|
255
|
-
#:set_dirname => true
|
256
|
-
}.merge(options)
|
257
|
-
|
258
|
-
input_files = options.delete(:input_files)
|
259
|
-
input_files = [input_files] unless input_files.nil? || input_files.kind_of?(Array)
|
260
|
-
|
261
|
-
expected_files = options.delete(:expected_files)
|
262
|
-
expected_files = [expected_files] unless expected_files.nil? || expected_files.kind_of?(Array)
|
263
233
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
input_files = infer_glob(:input) if input_files.nil?
|
282
|
-
flunk "No input files specified." if input_files.empty?
|
283
|
-
|
284
|
-
output_files = task.execute(*input_files).collect {|a| a._current}
|
285
|
-
expected_files = infer_glob(:expected) if expected_files.nil?
|
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
|
286
250
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
translated_files = output_files.collect {|file| infer_translate(file, :output, :expected)}
|
293
|
-
assert_equal expected_files, translated_files, "Missing or extra expected files"
|
294
|
-
else
|
295
|
-
assert_equal expected_files.length, output_files.length, "Missing or extra expected files"
|
296
|
-
end
|
251
|
+
app_config = {
|
252
|
+
:root => method_root,
|
253
|
+
:options => {:quiet => true},
|
254
|
+
:absolute_paths => {:data => method_filepath(:output)}
|
255
|
+
}.merge(app_config)
|
297
256
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
end
|
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
|
303
261
|
end
|
304
262
|
end
|
305
263
|
end
|
data/lib/tap/test/env_vars.rb
CHANGED
@@ -1,16 +1,27 @@
|
|
1
1
|
module Tap
|
2
2
|
module Test
|
3
|
+
|
4
|
+
# Provides a method for case-insensitive access to the ENV variables
|
3
5
|
module EnvVars
|
4
6
|
|
5
|
-
# Access to the case-insensitive ENV variables
|
7
|
+
# Access to the case-insensitive ENV variables. Raises an error
|
8
|
+
# if multiple case-insensitive values are defined in ENV.
|
6
9
|
def env(type)
|
7
10
|
type = type.downcase
|
8
|
-
ENV.
|
9
|
-
|
11
|
+
selected = ENV.select {|key, value| key.downcase == type}
|
12
|
+
|
13
|
+
case selected.length
|
14
|
+
when 0 then nil
|
15
|
+
when 1 then selected[0][1]
|
16
|
+
else
|
17
|
+
raise "Multiple env values for '#{type}'"
|
10
18
|
end
|
11
|
-
nil
|
12
19
|
end
|
13
|
-
|
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
|
24
|
+
end
|
14
25
|
end
|
15
26
|
end
|
16
27
|
end
|
@@ -0,0 +1,373 @@
|
|
1
|
+
require 'tap/root'
|
2
|
+
require 'tap/test/env_vars'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Test # :nodoc:
|
7
|
+
module Unit # :nodoc:
|
8
|
+
# Methods extending TestCase. See the TestTutorial for more information.
|
9
|
+
class TestCase
|
10
|
+
class << self
|
11
|
+
|
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
|
14
|
+
# instantiate trs can be specified as options. By default file_test_root
|
15
|
+
# and default_directories will be used.
|
16
|
+
#
|
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+.
|
22
|
+
# Be sure to specify the root directory explicitly if you call acts_as_file_test
|
23
|
+
# from a file that is NOT meant to be test file.
|
24
|
+
def acts_as_file_test(options={})
|
25
|
+
options = {
|
26
|
+
:root => file_test_root,
|
27
|
+
:directories => {}
|
28
|
+
}.merge(options.symbolize_keys)
|
29
|
+
|
30
|
+
directories = default_directories.merge(options[:directories])
|
31
|
+
trs = Tap::Root.new(options[:root], directories)
|
32
|
+
|
33
|
+
write_inheritable_attribute(:trs, trs)
|
34
|
+
class_inheritable_reader :trs
|
35
|
+
|
36
|
+
include Tap::Test::FileMethods
|
37
|
+
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
|
+
|
48
|
+
# Infers the test root directory from the calling file. Ex:
|
49
|
+
# 'some_class.rb' => 'some_class'
|
50
|
+
# 'some_class_test.rb' => 'some_class'
|
51
|
+
def file_test_root
|
52
|
+
# the calling file is not the direct caller of +method_root+... this method is
|
53
|
+
# only accessed from within another method call, hence the target caller is caller[1]
|
54
|
+
# rather than caller[0].
|
55
|
+
|
56
|
+
# caller[1] is considered the calling file (which should be the test case)
|
57
|
+
# note that the output of calller.first is like:
|
58
|
+
# ./path/to/file.rb:10
|
59
|
+
# ./path/to/file.rb:10:in 'method'
|
60
|
+
calling_file = caller[1].gsub(/:\d+[:in .*]?$/, "" )
|
61
|
+
calling_file.chomp!("#{File.extname(calling_file)}")
|
62
|
+
calling_file.chomp("_test")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Tap
|
70
|
+
module Test
|
71
|
+
|
72
|
+
# == Under Construction
|
73
|
+
#
|
74
|
+
# FileMethods sets up a TestCase with methods for accessing and utilizing
|
75
|
+
# test-specific files and directories. Each class including FileMethods
|
76
|
+
# 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.
|
79
|
+
#
|
80
|
+
# class FileDependentTest < Test::Unit::TestCase
|
81
|
+
# 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"
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# == File Transform Tests
|
91
|
+
#
|
92
|
+
# FileMethods is specifically designed for tests that transform a set of input
|
93
|
+
# files into output files. For this type of test, input and expected files can
|
94
|
+
# 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.
|
97
|
+
#
|
98
|
+
# For example, lets define a test that transforms input files into output files
|
99
|
+
# in a trivial way, simply by replacing 'input' with 'output' in the file.
|
100
|
+
#
|
101
|
+
# class FileTest < Test::Unit::TestCase
|
102
|
+
# acts_as_file_test
|
103
|
+
#
|
104
|
+
# def test_transform
|
105
|
+
# assert_output_files_equal do |input_files|
|
106
|
+
# input_files.collect do |filepath|
|
107
|
+
# input = File.read(filepath)
|
108
|
+
# output_file = method_filepath(:ouput_files, File.basename(filepath))
|
109
|
+
#
|
110
|
+
# File.open(output_file, "w") do |f|
|
111
|
+
# f << input.gsub(/input/, "output")
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# Now make some input and expected files for the 'test_transform' method:
|
119
|
+
#
|
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}"}
|
126
|
+
#
|
127
|
+
# expected = "file_test/test_transform/expected_files/" + basename
|
128
|
+
# File.open(expected, "w") {|f| f << "test output #{i}"}
|
129
|
+
# end
|
130
|
+
#
|
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.
|
136
|
+
#
|
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.
|
139
|
+
#
|
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:
|
145
|
+
#
|
146
|
+
# % tap run test keep_failures=true
|
147
|
+
#
|
148
|
+
module FileMethods
|
149
|
+
include Tap::Test::EnvVars
|
150
|
+
|
151
|
+
# Convenience accessor for the test root structure
|
152
|
+
def trs
|
153
|
+
self.class.trs
|
154
|
+
end
|
155
|
+
|
156
|
+
# Creates the trs.directories, specific to the method calling make_test_directories
|
157
|
+
def make_test_directories
|
158
|
+
trs.directories.values.each do |dir|
|
159
|
+
FileUtils.mkdir_p( File.join(trs.root, method_name, dir) )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Setup deletes the the output directory if it exists, and tries to remove the
|
164
|
+
# method root directory so the directory structure is reset before running the
|
165
|
+
# test, even if outputs were left over from previous tests.
|
166
|
+
def setup
|
167
|
+
super
|
168
|
+
clear_method_dir(:output)
|
169
|
+
try_remove_dir(method_root)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Teardown deletes the the output directories unless flagged otherwise. Note
|
173
|
+
# that teardown also checks the environment variables for flags. To keep all outputs
|
174
|
+
# (or failures) for all tests, flag keep outputs from the command line like:
|
175
|
+
#
|
176
|
+
# % tap run test KEEP_OUTPUTS=true
|
177
|
+
# % tap run test KEEP_FAILURES=true
|
178
|
+
def teardown
|
179
|
+
# clear out the output folder if it exists, unless flagged otherwise
|
180
|
+
unless env("KEEP_OUTPUTS") || (!@test_passed && env("KEEP_FAILURES"))
|
181
|
+
begin
|
182
|
+
clear_method_dir(:output)
|
183
|
+
rescue
|
184
|
+
raise("teardown failure: could not remove output files")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
try_remove_dir(method_root)
|
189
|
+
try_remove_dir(trs.root)
|
190
|
+
end
|
191
|
+
|
192
|
+
# The method_root directory is defined as trs.filepath(method_name)
|
193
|
+
def method_root(method=method_name)
|
194
|
+
trs.filepath(method)
|
195
|
+
end
|
196
|
+
|
197
|
+
# The method directory is defined as 'dir/method', where method is the calling method
|
198
|
+
# by default. method_dir returns the method directory if it exists, otherwise it returns
|
199
|
+
# trs[dir].
|
200
|
+
def method_dir(dir, method=method_name)
|
201
|
+
File.join(method_root(method), trs.directories[dir] || dir.to_s)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns a glob of files matching the input pattern, underneath the method directory
|
205
|
+
# if it exists, otherwise the <tt>trs[dir]</tt> directory.
|
206
|
+
def method_glob(dir, *patterns)
|
207
|
+
dir = trs.relative_filepath(:root, method_dir(dir))
|
208
|
+
trs.glob(dir, *patterns)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns a filepath constructed from the method directory if it exists,
|
212
|
+
# otherwise the filepath will be constructed from <tt>trs[dir]</tt>.
|
213
|
+
def method_filepath(dir, *filenames)
|
214
|
+
File.join(method_dir(dir), *filenames)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Removes the method directory from the input filepath, returning the resuting filename.
|
218
|
+
# If the method directory does not exist, <tt>trs[dir]</tt> will be removed.
|
219
|
+
def method_relative_filepath(dir, filepath)
|
220
|
+
dir = trs.relative_filepath(:root, method_dir(dir))
|
221
|
+
trs.relative_filepath(dir, filepath)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns an output file corresponding to the input file, translated from the
|
225
|
+
# input directory to the output directory.
|
226
|
+
#
|
227
|
+
# If the input method directory exists, it will be removed from the filepath.
|
228
|
+
# If the output method directory exists, it will be inserted in the filepath.
|
229
|
+
def method_translate(filepath, input_dir, output_dir)
|
230
|
+
input_dir = trs.relative_filepath(:root, method_dir(input_dir))
|
231
|
+
output_dir = trs.relative_filepath(:root, method_dir(output_dir))
|
232
|
+
trs.translate(filepath, input_dir, output_dir)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Attempts to recursively remove the specified method directory and all
|
236
|
+
# files within it. Raises an error if the removal does not succeed.
|
237
|
+
def clear_method_dir(dir)
|
238
|
+
# clear out the folder if it exists
|
239
|
+
dir_path = method_dir(dir, method_name)
|
240
|
+
FileUtils.rm_r(dir_path) if File.exists?(dir_path)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Attempts to remove the specified directory. The root
|
244
|
+
# will not be removed if the directory does not exist, or
|
245
|
+
# is not empty.
|
246
|
+
def try_remove_dir(dir)
|
247
|
+
# Remove the directory if possible
|
248
|
+
begin
|
249
|
+
FileUtils.rmdir(dir) if File.exists?(dir) && Dir.glob(File.join(dir, "*")).empty?
|
250
|
+
rescue
|
251
|
+
# rescue cases where there is a hidden file, for example .svn
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Generates a temporary filepath formatted like "output_dir\filename.pid.n.ext" where n
|
256
|
+
# is a counter that will be incremented from until a non-existant filepath is achieved.
|
257
|
+
#
|
258
|
+
# Notes:
|
259
|
+
# - By default filename is the calling method
|
260
|
+
# - The extension is chomped off the end of the filename
|
261
|
+
# - If the directory for the filepath does not exist, the directory will be created
|
262
|
+
# - Like all files in the output directory, tempfiles will be deleted by the default
|
263
|
+
# +teardown+ method
|
264
|
+
def tempfile(filename=method_name)
|
265
|
+
n = 0
|
266
|
+
ext = File.extname(filename)
|
267
|
+
basename = filename.chomp(ext)
|
268
|
+
filepath = make_tmpname(basename, n, ext)
|
269
|
+
while File.exists?(filepath)
|
270
|
+
n += 1
|
271
|
+
filepath = make_tmpname(basename, n, ext)
|
272
|
+
end
|
273
|
+
|
274
|
+
dirname = File.dirname(filepath)
|
275
|
+
FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
|
276
|
+
filepath
|
277
|
+
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
|
+
|
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)
|
293
|
+
a = [a] unless a.kind_of?(Array)
|
294
|
+
b = [b] unless b.kind_of?(Array)
|
295
|
+
|
296
|
+
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])
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def assert_output_files_equal(options={}) # :yields: input_files
|
303
|
+
make_test_directories
|
304
|
+
|
305
|
+
options = {
|
306
|
+
:input_files => nil,
|
307
|
+
:expected_files => nil,
|
308
|
+
:require_expected_files => true,
|
309
|
+
:check_for_missing_outputs => true,
|
310
|
+
:iterate_inputs => false,
|
311
|
+
:include_directories => false
|
312
|
+
}.merge(options)
|
313
|
+
|
314
|
+
# set or search for input and expected files, and arrayify
|
315
|
+
input_files, expected_files = [:input, :expected].collect do |key|
|
316
|
+
options_key = "#{key}_files".to_sym
|
317
|
+
|
318
|
+
files = options.delete(options_key)
|
319
|
+
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]
|
323
|
+
files.delete_if {|file| File.directory?(file)}
|
324
|
+
end
|
325
|
+
files
|
326
|
+
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
|
+
|
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"
|
349
|
+
end
|
350
|
+
|
351
|
+
# 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"
|
355
|
+
|
356
|
+
# check that the expected and output file contents are equal
|
357
|
+
errors = []
|
358
|
+
each_pair(expected_files, output_files) do |expected_file, output_file|
|
359
|
+
unless files_equal?(expected_file, output_file)
|
360
|
+
errors << "<#{expected_file}> not equal to\n<#{output_file}>"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
flunk "File compare failed:\n" + errors.join("\n") unless errors.empty?
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def make_tmpname(basename, n, ext="")
|
369
|
+
method_filepath(:output, sprintf('%s%d.%d%s', basename, $$, n, ext))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|