Preparation for parallel cmdtest:

- 'runner' parameter to methods instead of constructors
- unique work-directory for each test method
- detect class/method name clashes early
- moved tmp-dir knowledge to "testcase.rb"
This commit is contained in:
Johan Holmberg 2011-11-02 00:03:23 +01:00
parent 27f1f3f80f
commit 892dc530c5
4 changed files with 118 additions and 74 deletions

View File

@ -49,41 +49,51 @@ module Cmdtest
class TestMethod
def initialize(test_method, test_class, runner)
@test_method, @test_class, @runner = test_method, test_class, runner
def initialize(test_method, test_class)
@test_method, @test_class = test_method, test_class
end
def to_s
class_name = @test_class.testcase_class.name.sub(/^.*::/, "")
"<<TestMethod: #{class_name}.#{@test_method}>>"
end
def as_filename
klass_name = @test_class.as_filename
"#{klass_name}.#{@test_method}"
end
def method_id
[@test_class.file, @test_class.testcase_class, @test_method]
end
def skip?
patterns = @runner.opts.patterns
def skip?(runner)
patterns = runner.opts.patterns
selected = (patterns.size == 0 ||
patterns.any? {|pattern| pattern =~ @test_method } )
!selected || @runner.method_filter.skip?(*method_id)
!selected || runner.method_filter.skip?(*method_id)
end
def run
@runner.notify("testmethod", @test_method) do
obj = @test_class.testcase_class.new(self, @runner)
def run(runner)
runner.notify("testmethod", @test_method) do
obj = @test_class.testcase_class.new(self, runner)
obj._work_dir.chdir do
obj.setup
begin
obj.send(@test_method)
@runner.assert_success
runner.assert_success
rescue Cmdtest::AssertFailed => e
@runner.assert_failure(e.message)
@runner.method_filter.error(*method_id)
runner.assert_failure(e.message)
runner.method_filter.error(*method_id)
rescue => e
io = StringIO.new
io.puts "CAUGHT EXCEPTION:"
io.puts " " + e.message + " (#{e.class})"
io.puts "BACKTRACE:"
io.puts e.backtrace.map {|line| " " + line }
@runner.assert_error(io.string)
@runner.method_filter.error(*method_id)
runner.assert_error(io.string)
runner.method_filter.error(*method_id)
end
obj.teardown
end
@ -98,22 +108,26 @@ module Cmdtest
attr_reader :testcase_class, :file
def initialize(testcase_class, file, runner)
@testcase_class, @file, @runner = testcase_class, file, runner
def initialize(testcase_class, file)
@testcase_class, @file = testcase_class, file
end
def run
@runner.notify("testclass", @testcase_class) do
get_test_methods.each do |method|
test_method = TestMethod.new(method, self, @runner)
test_method.run unless test_method.skip?
def as_filename
@testcase_class.name.sub(/^.*::/, "")
end
def run(runner)
runner.notify("testclass", @testcase_class) do
get_test_methods(runner).each do |method|
test_method = TestMethod.new(method, self)
test_method.run(runner) unless test_method.skip?(runner)
end
end
end
def get_test_methods
def get_test_methods(runner)
@testcase_class.public_instance_methods(false).sort.select do |method|
in_list = @runner.opts.tests.empty? || @runner.opts.tests.include?(method)
in_list = runner.opts.tests.empty? || runner.opts.tests.include?(method)
method =~ /^test_/ && in_list
end
end
@ -126,20 +140,22 @@ module Cmdtest
attr_reader :path
attr_reader :test_classes
def initialize(path)
@path = path
@test_classes = Cmdtest::Testcase.new_subclasses do
Kernel.load(@path, true)
end.map do |testcase_class|
TestClass.new(testcase_class, self)
end
end
def run(runner)
@runner = runner
@runner.notify("testfile", @path) do
testcase_classes = Cmdtest::Testcase.new_subclasses do
Kernel.load(@path, true)
end
for testcase_class in testcase_classes
test_class = TestClass.new(testcase_class, self, @runner)
if ! test_class.get_test_methods.empty?
test_class.run
runner.notify("testfile", @path) do
for test_class in @test_classes
if ! test_class.get_test_methods(runner).empty?
test_class.run(runner)
end
end
end
@ -198,11 +214,30 @@ module Cmdtest
# find local files "required" by testcase files
$LOAD_PATH.unshift(@project_dir.test_files_dir)
# force loading of all test files
test_files = @project_dir.test_filenames.map {|x| TestFile.new(x) }
# make sure class names are unique
used_test_class_filenames = {}
for test_file in test_files
for test_class in test_file.test_classes
filename = test_class.as_filename
prev_test_file = used_test_class_filenames[filename]
if prev_test_file
puts "ERROR: same class name used twice: #{filename}"
puts "ERROR: prev file: #{prev_test_file.path}"
puts "ERROR: curr file: #{test_file.path}"
exit(1)
end
used_test_class_filenames[filename] = test_file
end
end
@n_assert_failures = 0
@n_assert_errors = 0
@n_assert_successes = 0
notify("testsuite") do
for test_file in @project_dir.test_files
for test_file in test_files
test_file.run(self)
end
end
@ -234,20 +269,20 @@ module Cmdtest
def initialize(argv)
@argv = argv
@test_files = nil
@test_filenames = nil
end
def test_files
@test_files ||= _fs_test_files
def test_filenames
@test_filenames ||= _fs_test_filenames
end
def test_files_dir
File.dirname(test_files[0].path)
File.dirname(test_filenames[0])
end
private
def _fs_test_files
def _fs_test_filenames
if ! @argv.empty?
files = _expand_files_or_dirs(@argv)
if files.empty?
@ -258,28 +293,24 @@ module Cmdtest
end
try = Dir.glob("t/CMDTEST_*.rb")
return _test_files(try) if ! try.empty?
return try if ! try.empty?
try = Dir.glob("test/CMDTEST_*.rb")
return _test_files(try) if ! try.empty?
return try if ! try.empty?
try = Dir.glob("CMDTEST_*.rb")
return _test_files(try) if ! try.empty?
return try if ! try.empty?
puts "ERROR: no CMDTEST_*.rb files found"
exit 1
end
def _test_files(files)
files.map {|file| TestFile.new(file) }
end
def _expand_files_or_dirs(argv)
files = []
for arg in @argv
if File.file?(arg)
if File.basename(arg) =~ /^.*\.rb$/
files << TestFile.new(arg)
files << arg
else
puts "ERROR: illegal file: #{arg}"
exit(1)
@ -289,7 +320,7 @@ module Cmdtest
path = File.join(arg,entry)
next unless File.file?(path)
next unless entry =~ /^CMDTEST_.*\.rb$/
files << TestFile.new(path)
files << path
end
else
puts "ERROR: unknown file: #{arg}"

View File

@ -60,18 +60,34 @@ module Cmdtest
#------------------------------
ORIG_CWD = Dir.pwd
attr_reader :_work_dir
def initialize(test_method, runner)
@_test_method = test_method
@_runner = runner
@_work_dir = Workdir.new(runner)
@_work_dir = Workdir.new(self, runner)
@_in_cmd = false
@_comment_str = nil
@_env_path = @_runner.orig_env_path
@_t1 = @_t2 = 0
end
#------------------------------
def tmp_cmdtest_dir
File.join(ORIG_CWD, "tmp-cmdtest-%d" % [$cmdtest_level])
end
def tmp_dir
File.join(tmp_cmdtest_dir, @_test_method.as_filename)
end
def tmp_work_dir
File.join(tmp_dir, "work")
end
#------------------------------
# Import file into the "workdir" from the outside world.
# The source is found relative to the current directory when "cmdtest"
@ -79,7 +95,7 @@ module Cmdtest
# the current directory at the time of the call.
def import_file(src, tgt)
src_path = File.expand_path(src, Workdir::ORIG_CWD)
src_path = File.expand_path(src, ORIG_CWD)
tgt_path = tgt # rely on CWD
FileUtils.mkdir_p(File.dirname(tgt_path))
FileUtils.cp(src_path, tgt_path)
@ -93,7 +109,7 @@ module Cmdtest
# time of the call.
def create_file(filename, lines)
#Util.wait_for_new_second
#_wait_for_new_second
FileUtils.mkdir_p( File.dirname(filename) )
File.open(filename, "w") do |f|
case lines
@ -111,7 +127,7 @@ module Cmdtest
# time of the call.
def touch_file(filename)
#Util.wait_for_new_second
#_wait_for_new_second
FileUtils.touch(filename)
end
@ -571,11 +587,17 @@ module Cmdtest
#------------------------------
def _wait_for_new_second
Util.wait_for_new_second(tmp_dir, tmp_work_dir)
end
#------------------------------
def cmd(cmdline)
if Array === cmdline
cmdline = _args_to_quoted_string(cmdline)
end
Util.wait_for_new_second
_wait_for_new_second
_update_hardlinks
@_cmdline = cmdline
@_cmd_done = false

View File

@ -30,22 +30,22 @@ module Cmdtest
@@opts = opts
end
def self._timestamp_file
File.join(Workdir.tmp_cmdtest_dir, "TIMESTAMP")
def self._timestamp_file(tmp_dir)
File.join(tmp_dir, "TIMESTAMP")
end
def self.wait_for_new_second
def self.wait_for_new_second(tmp_dir, tmp_work_dir)
return if ! TRUST_MTIME || @@opts.fast
loop do
File.open(_timestamp_file, "w") {|f| f.puts Time.now }
break if File.mtime(_timestamp_file) != _newest_file_time
File.open(_timestamp_file(tmp_dir), "w") {|f| f.puts Time.now }
break if File.mtime(_timestamp_file(tmp_dir)) != _newest_file_time(tmp_work_dir)
sleep 0.2
end
end
def self._newest_file_time
def self._newest_file_time(tmp_work_dir)
tnew = Time.at(0)
Find.find(Workdir.tmp_work_dir) do |path|
Find.find(tmp_work_dir) do |path|
next if ! File.file?(path)
t = File.mtime(path)
tnew = t > tnew ? t : tnew

View File

@ -27,20 +27,11 @@ require "fileutils"
module Cmdtest
class Workdir
ORIG_CWD = Dir.pwd
def self.tmp_cmdtest_dir
File.join(ORIG_CWD, "tmp-cmdtest-%d" % [$cmdtest_level])
end
def self.tmp_work_dir
File.join(tmp_cmdtest_dir, "workdir")
end
def initialize(runner)
def initialize(testcase, runner)
@testcase = testcase
@runner = runner
@dir = Workdir.tmp_work_dir
hardlinkdir = File.join(Workdir.tmp_cmdtest_dir, "hardlinks")
@dir = @testcase.tmp_work_dir
hardlinkdir = File.join(testcase.tmp_dir, "hardlinks")
FileUtils.rm_rf(@dir)
FileUtils.rm_rf(hardlinkdir)
FileUtils.mkdir_p(@dir)
@ -78,16 +69,16 @@ module Cmdtest
end
def _tmp_command_sh
File.join(Workdir.tmp_cmdtest_dir,
File.join(@testcase.tmp_dir,
_windows ? "tmp-command.bat" : "tmp-command.sh")
end
def _tmp_stdout_log
File.join(Workdir.tmp_cmdtest_dir, "tmp-stdout.log")
File.join(@testcase.tmp_dir, "tmp-stdout.log")
end
def _tmp_stderr_log
File.join(Workdir.tmp_cmdtest_dir, "tmp-stderr.log")
File.join(@testcase.tmp_dir, "tmp-stderr.log")
end
def _set_env_path_str(env_path)