lib/tap/test/file_test.rb in tap-test-0.4.0 vs lib/tap/test/file_test.rb in tap-test-0.6.0

- old
+ new

@@ -2,112 +2,69 @@ require 'tap/test/utils' module Tap module Test - # FileTest facilitates access and utilization of test-specific files and - # directories. FileTest provides each test method with a Tap::Root - # (method_root) specific for the method, and defines a new assertion - # method (assert_files) to facilitate tests which involve the production - # and/or modification of files. + # FileTest simplifies testing of code that works with files. FileTest + # provides a method-specific Tap::Root (method_root) that expedites the + # creation and access of files, as well as a couple standard cleanup + # methods. # - # [file_test_doc_test.rb] - # class FileTestDocTest < Test::Unit::TestCase - # acts_as_file_test - # - # def test_something - # # each test class has a class test root (ctr) - # # and each test method has a method-specific - # # root (method_root) - # - # ctr.root # => File.expand_path(__FILE__.chomp('_test.rb')) - # method_root.root # => File.join(ctr.root, "/test_something") - # method_root[:input] # => File.join(ctr.root, "/test_something/input") - # - # # files in the :output and :tmp directories are cleared - # # before and after each test; this passes each time the - # # test is run with no additional cleanup: - # - # assert !File.exists?(method_root[:tmp]) - # - # tmp_file = method_root.prepare(:tmp, 'sample.txt') {|file| file << "content" } - # assert_equal "content", File.read(tmp_file) - # - # # the assert_files method compares files produced - # # by the block the expected files, ensuring they - # # are the same (see the documentation for the - # # simplest use of assert_files) - # - # expected_file = method_root.prepare(:expected, 'output.txt') {|file| file << 'expected output' } - # - # # passes - # assert_files do - # method_root.prepare(:output, 'output.txt') {|file| file << 'expected output' } - # end - # end - # end - # # ==== Cleanup # - # By default the method_root directory is cleaned up at the end of the - # test, making FileTest very conveient for creating temporary test files. - # To prevent cleanup, either set the KEEP_OUTPUTS or KEEP_FAILURES ENV - # variable to 'true'. The cleanup directories can be specified manually - # using cleanup_dirs class variable: + # By default the entire method_root directory is cleaned up at the end of + # the test. To prevent cleanup, either set the KEEP_OUTPUTS or + # KEEP_FAILURES ENV variable to 'true'. The cleanup directories can be + # specified manually using cleanup_dirs class variable: # # class LimitedCleanupTest < Test::Unit::TestCase # - # # only cleanup the method_root[:output] directory + # # only cleanup the output directory under root # acts_as_file_test :cleanup_dirs => [:output] # end # # This technique is useful when you want to keep certain static files # under version control, for instance. # # ==== Requirements # # FileTest requires that a method_name method is provided by the including - # class, in order to properly set the directory for method_root. + # class, in order to properly set the directory for root. # Test::Unit::TestCase satisfies this requirement already. module FileTest def self.included(base) # :nodoc: super base.extend FileTest::ClassMethods - base.cleanup_dirs = [:root] end - # The test-method-specific Tap::Root which may be used to access test - # files. method_root is a duplicate of ctr reconfigured so that - # method_root.root is ctr[method_name.to_sym] + # The method-specific Tap::Root attr_reader :method_root # Sets up method_root and calls cleanup. Be sure to call super when # overriding this method. def setup super - @method_root = ctr.dup.reconfigure(:root => ctr[method_name.to_sym]) + @method_root = class_root.sub(method_name) cleanup end - # Cleans up the method_root.root directory by removing the class - # cleanup_dirs (by default :root, ie the method_root directory itself). - # The root directory will be removed if it is empty. + # Cleans up the method_root directory by removing the cleanup_dirs + # specified for the class. (by default the entire method_root directory + # is removed). The method_root directory will be removed if it is empty. # # Override as necessary in subclasses. def cleanup - self.class.cleanup_dirs.each do |dir| - clear_dir(method_root[dir]) - end - try_remove_dir(method_root.root) + cleanup_dirs = self.class.cleanup_dirs + cleanup_dirs.each {|dir| clear_dir(method_root.path(dir)) } + + try_remove_dir(method_root.path) end # Calls cleanup unless flagged otherwise by an ENV variable (see above). # Be sure to call super when overriding this method. def teardown - # check that method_root still exists (nil may - # indicate setup was overridden without super) unless method_root raise "teardown failure: method_root is nil (does setup call super?)" end # clear out the output folder if it exists, unless flagged otherwise @@ -117,273 +74,32 @@ rescue raise("cleanup failure: #{$!.message}") end end - try_remove_dir(ctr.root) + try_remove_dir(class_root.path) end - # Convenience method to access the class_test_root. - def ctr - self.class.class_test_root or raise "setup failure: no class_test_root has been set for #{self.class}" + # Convenience method to access the class_root. + def class_root + self.class.class_root or raise "setup failure: no class_root has been set for #{self.class}" end - # Runs a file-based test that compares files created by the block with - # files in an expected directory. The block receives files from the - # input directory, and should return a list of files relative to the - # output directory. Only the files returned by the block are compared; - # additional files in the output directory are effectively ignored. - # - # === Example - # Lets define a test that transforms input files into output files in a - # trivial way, simply by replacing 'input' with 'output' in the file. - # - # class FileTestDocTest < Test::Unit::TestCase - # acts_as_file_test - # - # def test_assert_files - # assert_files do |input_files| - # input_files.collect do |path| - # input = File.read(path) - # output_file = method_root.path(:output, File.basename(path)) - # - # File.open(output_file, "w") do |f| - # f << input.gsub(/input/, "output") - # end - # - # output_file - # end - # end - # end - # end - # - # Now say you had some input and expected files for test_assert_files: - # - # file_test_doc/test_assert_files - # |- expected - # | |- one.txt - # | `- two.txt - # `- input - # |- one.txt - # `- two.txt - # - # [input/one.txt] - # test input 1 - # - # [input/two.txt] - # test input 2 - # - # [expected/one.txt] - # test output 1 - # - # [expected/two.txt] - # test output 2 - # - # When you run the test, the assert_files passes the input files to the - # block. When the block completes, assert_files compares the output - # files returned by the block with the files in the expected directory. - # In this case, the files are equal and the test passes. - # - # Say you changed the content of one of the expected files: - # - # [expected/one.txt] - # test flunk 1 - # - # Now the test fails because the output files aren't equal to the - # expected files. The test also fails if there are missing or extra - # files. - # - # === Options - # A variety of options adjust the behavior of assert_files: - # - # :input_dir specify the directory to glob for input files - # (default method_root[:input]) - # :output_dir specify the output directory - # (default method_root[:output]) - # :expected_dir specify the directory to glob for expected files - # (default method_root[:expected]) - # :input_files directly specify the input files for the block - # :expected_files directly specify the expected files for comparison - # :include_input_directories specifies directories to be included in the - # input_files array (by default dirs are excluded) - # :include_expected_directories specifies directories to be included in the - # expected-output file list comparison - # (by default dirs are excluded) - # - # assert_files will fail if <tt>:expected_files</tt> was not specified - # in the options and no files were found in <tt>:expected_dir</tt>. This - # check tries to prevent silent false-positive results when you forget - # to put expected files in their place. - # - # === File References - # - # Sometimes the same files will get used across multiple tests. To - # allow separate management of test files and prevent duplication, file - # references can be provided in place of test files. For instance, with - # a test directory like: - # - # method_root - # |- expected - # | |- one.txt.ref - # | `- two.txt.ref - # |- input - # | |- one.txt.ref - # | `- two.txt.ref - # `- ref - # |- one.txt - # `- two.txt - # - # The input and expected files (all references in this case) can be - # dereferenced to the 'ref' paths like so: - # - # assert_files :reference_dir => method_root[:ref] do |input_files| - # input_files # => ['method_root/ref/one.txt', 'method_root/ref/two.txt'] - # - # input_files.collect do |input_file| - # output_file = method_root.path(:output, File.basename(input_file) - # FileUtils.cp(input_file, output_file) - # output_file - # end - # end - # - # Dereferencing occurs relative to the input_dir/expected_dir - # configurations; a reference_dir must be specified for dereferencing to - # occur (see Utils.dereference for more details). - # - #-- - # TODO: - # * add debugging information to indicate, for instance, when - # dereferencing is going on. - def assert_files(options={}, &block) # :yields: input_files - transform_test(block, options) do |expected_file, output_file| - unless FileUtils.cmp(expected_file, output_file) - flunk "<#{expected_file}> not equal to\n<#{output_file}>" - end - end - end - - # The default assert_files options - def assert_files_options - { - :input_dir => method_root[:input], - :output_dir => method_root[:output], - :expected_dir => method_root[:expected], - - :input_files => nil, - :expected_files => nil, - :include_input_directories => false, - :include_expected_directories => false, - - :reference_dir => nil, - :reference_extname => '.ref' - } - end - - private - - # Yields to the input block for each pair of entries in the input - # arrays. An error is raised if the input arrays do not have equal - # numbers of entries. - def each_pair(a, b, &block) # :yields: entry_a, entry_b, - each_pair_with_index(a,b) do |entry_a, entry_b, index| - yield(entry_a, entry_b) - end - end - - # Same as each_pair but yields the index of the entries as well. - def each_pair_with_index(a, b, error_msg=nil, &block) # :yields: entry_a, entry_b, index - a = [a] unless a.kind_of?(Array) - b = [b] unless b.kind_of?(Array) - - unless a.length == b.length - raise ArgumentError, (error_msg || "The input arrays must have an equal number of entries.") - end - - 0.upto(a.length-1) do |index| - yield(a[index], b[index], index) - end - end - # Attempts to recursively remove the specified method directory and all # files within it. Raises an error if the removal does not succeed. def clear_dir(dir) # clear out the folder if it exists FileUtils.rm_r(dir) if File.exists?(dir) end - # Attempts to remove the specified directory. The root - # will not be removed if the directory does not exist, or - # is not empty. + # Attempts to remove the specified directory. The directory will not be + # removed unless fully empty (including hidden files). def try_remove_dir(dir) - # Remove the directory if possible begin - FileUtils.rmdir(dir) if File.exists?(dir) && Dir.glob(File.join(dir, "*")).empty? + FileUtils.rmdir(dir) if File.directory?(dir) && Dir.glob(File.join(dir, "*")).empty? rescue # rescue cases where there is a hidden file, for example .svn end end - - def transform_test(block, options={}) # :yields: expected_files, output_files - options = assert_files_options.merge(options) - input_dir = options[:input_dir] - output_dir = options[:output_dir] - expected_dir = options[:expected_dir] - - reference_dir = options[:reference_dir] - reference_pattern = options[:reference_pattern] - - Utils.dereference([input_dir, expected_dir], reference_dir, reference_pattern || '**/*.ref') do - - # Get the input and expected files in this manner: - # - look for manually specified files - # - glob for files if none were specified - # - expand paths and sort - # - remove directories unless specified not to do so - input_files, expected_files = [:input, :expected].collect do |key| - files = options["#{key}_files".to_sym] - if files.nil? - pattern = File.join(options["#{key}_dir".to_sym], "**/*") - files = Dir.glob(pattern) - end - files = [files].flatten.collect {|file| File.expand_path(file) }.sort - - unless options["include_#{key}_directories".to_sym] - files.delete_if {|file| File.directory?(file)} - end - - files - end - - # check at least one expected file was found - if expected_files.empty? && options[:expected_files] == nil - flunk "No expected files specified." - end - - # get output files from the block, expand and sort - output_files = [*block.call(input_files)].collect do |output_file| - File.expand_path(output_file) - end.sort - - # check that the expected and output paths are the same - translated_expected_files = expected_files.collect do |expected_file| - Tap::Root::Utils.translate(expected_file, expected_dir, output_dir) - end - assert_equal translated_expected_files, output_files, "Missing, extra, or unexpected output files" - - # check that the expected and output file contents are equal - errors = [] - each_pair(expected_files, output_files) do |expected_file, output_file| - unless (File.directory?(expected_file) && File.directory?(output_file)) || FileUtils.cmp(expected_file, output_file) - begin - yield(expected_file, output_file) - rescue - errors << $! - end - end - end - flunk "File compare failed:\n" + errors.join("\n") unless errors.empty? - end - end - end end end \ No newline at end of file