null+****@clear*****
null+****@clear*****
Sat Aug 11 00:34:23 JST 2012
Kouhei Sutou 2012-08-11 00:34:23 +0900 (Sat, 11 Aug 2012) New Revision: 26077f241cc0bd2f8eed066e33ab4672e1521cc0 https://github.com/groonga/grntest/commit/26077f241cc0bd2f8eed066e33ab4672e1521cc0 Log: Support parallel test running But reporter isn't good yet. :< Modified files: lib/groonga/tester.rb Modified: lib/groonga/tester.rb (+355 -126) =================================================================== --- lib/groonga/tester.rb 2012-08-10 13:18:02 +0900 (58951f6) +++ lib/groonga/tester.rb 2012-08-11 00:34:23 +0900 (41d6ab6) @@ -116,6 +116,11 @@ module Groonga tester.reporter = reporter end + parser.on("--n-workers=N", Integer, + "Use N workers to run tests") do |n| + tester.n_workers = n + end + parser.on("--gdb[=COMMAND]", "Run groonga on gdb and use COMMAND as gdb", "(#{tester.default_gdb})") do |command| @@ -141,6 +146,7 @@ module Groonga attr_accessor :groonga, :groonga_httpd, :groonga_suggest_create_dataset attr_accessor :protocol, :testee attr_accessor :base_directory, :diff, :diff_options, :reporter + attr_accessor :n_workers attr_accessor :gdb, :default_gdb attr_writer :keep_database def initialize @@ -151,6 +157,7 @@ module Groonga @testee = "groonga" @base_directory = "." @reporter = :stream + @n_workers = 1 detect_suitable_diff initialize_debuggers end @@ -194,7 +201,7 @@ module Groonga end def run_test_suites(test_suites) - runner = SequentialTestSuitesRunner.new(self) + runner = TestSuitesRunner.new(self) runner.run(test_suites) end @@ -221,10 +228,97 @@ module Groonga false end + class Worker + attr_reader :id, :tester, :test_suites_rusult, :reporter + attr_reader :suite_name, :test_script_path, :status + def initialize(id, tester, test_suites_result, reporter) + @id = id + @tester = tester + @test_suites_result = test_suites_result + @reporter = reporter + @suite_name = nil + @test_script_path = nil + @interruptted = false + @status = "not running" + end + + def interrupt + @interruptted = true + end + + def interruptted? + @interruptted + end + + def test_name + return nil if @test_script_path.nil? + @test_script_path.basename(".*").to_s + end + + def run(queue) + succeeded = true + + @reporter.start_worker(self) + catch do |tag| + loop do + suite_name, test_script_path = queue.pop + break if test_script_path.nil? + + unless @suite_name == suite_name + @reporter.finish_suite(self) if @suite_name + @suite_name = suite_name + @reporter.start_suite(self) + end + @test_script_path = test_script_path + runner = TestRunner.new(@tester, self) + succeeded = false unless runner.run + + break if interruptted? + end + @status = "finished" + @reporter.finish_suite(@suite_name) if @suite_name + @suite_name = nil + end + @reporter.finish_worker(self) + + succeeded + end + + def start_test + @status = "running" + @test_result = nil + @reporter.start_test(self) + end + + def pass_test(result) + @status = "passed" + @test_suites_result.test_passed + @reporter.pass_test(self, result) + end + + def fail_test(result) + @status = "failed" + @test_suites_result.test_failed(test_name) + @reporter.fail_test(self, result) + end + + def no_check_test(result) + @status = "not checked" + @test_suites_result.test_not_checked + @reporter.no_check_test(self, result) + end + + def finish_test(result) + @test_suites_result.test_finished + @reporter.finish_test(self, result) + @test_script_path = nil + end + end + class Result attr_accessor :elapsed_time def initialize - @elapsed_time + @elapsed_time = 0 end def measure @@ -236,102 +330,171 @@ module Groonga end class TestSuitesResult < Result + attr_reader :n_tests, :n_passed_tests, :n_not_checked_tests + attr_reader :failed_tests + def initialize + super + @n_tests = 0 + @n_passed_tests = 0 + @n_not_checked_tests = 0 + @failed_tests = [] + end + + def n_failed_tests + @failed_tests.size + end + + def pass_ratio + if @n_tests.zero? + 0 + else + (@n_passed_tests / @n_tests.to_f) * 100 + end + end + + def test_finished + @n_tests += 1 + end + + def test_passed + @n_passed_tests += 1 + end + + def test_failed(name) + @failed_tests << name + end + + def test_not_checked + @n_not_checked_tests += 1 + end end class TestSuitesRunner def initialize(tester) @tester = tester + @reporter = create_reporter + @result = TestSuitesResult.new end - private - def create_reporter - case****@teste***** - when :stream - StreamReporter.new(@tester) - when :inplace - InplaceReporter.new(@tester) + def run(test_suites) + succeeded = true + + @result.measure do + succeeded = run_test_suites(test_suites) end + @reporter.finish(@result) + + succeeded end - end - class SequentialTestSuitesRunner < TestSuitesRunner - def run(test_suites) + private + def run_test_suites(test_suites) + queue = Queue.new + test_suites.each do |suite_name, test_script_paths| + test_script_paths.each do |test_script_path| + queue << [suite_name, test_script_path] + end + end + @tester.n_workers.times do + queue << nil + end + + workers = [] + @tester.n_workers.times do |i| + workers << Worker.new(i, @tester, @result, @reporter) + end + @reporter.start(workers.dup) + succeeded = true - reporter = create_reporter - result = TestSuitesResult.new - result.measure do - reporter.start - catch do |tag| - test_suites.each do |suite_name, test_script_paths| - reporter.start_suite(suite_name) - test_script_paths.each do |test_script_path| - runner = TestRunner.new(@tester, test_script_path) - succeeded = false unless runner.run(reporter) - throw(tag) if runner.interrupted? + worker_threads = [] + @tester.n_workers.times do |i| + worker = workers[i] + worker_threads << Thread.new do + succeeded = worker.run(queue) + workers.delete(worker) + if worker.interruptted? + workers.each do |other_worker| + other_worker.interrupt end - reporter.finish_suite(suite_name) end end end - reporter.finish(result) + worker_threads.each(&:join) + succeeded end + + def create_reporter + case****@teste***** + when :stream + StreamReporter.new(@tester) + when :inplace + InplaceReporter.new(@tester) + end + end end class TestResult < Result - attr_accessor :test_name + attr_accessor :worker_id, :test_name attr_accessor :expected, :actual - def initialize(test_name) + def initialize(worker) super() - @test_name = test_name + @worker_id = worker.id + @test_name = worker.test_name @actual = nil @expected = nil end + + def status + if @expected + if @actual == @expected + :pass + else + :fail + end + else + :no_check + end + end end class TestRunner MAX_N_COLUMNS = 79 - def initialize(tester, test_script_path) + def initialize(tester, worker) @tester = tester - @test_script_path = test_script_path + @worker = worker @max_n_columns = MAX_N_COLUMNS - @interrupted = false + @id = nil end - def run(reporter) + def run succeeded = true - test_name = @test_script_path.basename.to_s - result = TestResult.new(test_name) - reporter.start_test(test_name) + @worker.start_test + result = TestResult.new(@worker) result.measure do result.actual = run_groonga_script end result.actual = normalize_result(result.actual) result.expected = read_expected_result - if result.expected - if result.actual == result.expected - reporter.pass_test(result) - remove_reject_file - else - reporter.fail_test(result) - output_reject_file(result.actual) - succeeded = false - end + case result.status + when :pass + @worker.pass_test(result) + remove_reject_file + when :fail + @worker.fail_test(result) + output_reject_file(result.actual) + succeeded = false else - reporter.no_check_test(result) + @worker.no_check_test(result) output_actual_file(result.actual) end - reporter.finish_test(result) + @worker.finish_test(result) succeeded end - def interrupted? - @interrupted - end - private def run_groonga_script create_temporary_directory do |directory_path| @@ -344,10 +507,10 @@ module Groonga context.groonga_suggest_create_dataset = @tester.groonga_suggest_create_dataset run_groonga(context) do |executor| - executor.execute(@test_script_path) + executor.execute(test_script_path) end rescue Interrupt - @interrupted = true + @worker.interrupted end context.result end @@ -355,6 +518,7 @@ module Groonga def create_temporary_directory path = "tmp/grntest" + path << ".#{@worker.id}" if****@teste*****_workers > 1 FileUtils.rm_rf(path, :secure => true) FileUtils.mkdir_p(path) begin @@ -370,7 +534,7 @@ module Groonga end def keep_database_path - @test_script_path.to_s.gsub(/\//, ".") + test_script_path.to_s.gsub(/\//, ".") end def run_groonga(context, &block) @@ -605,13 +769,17 @@ EOF end end + def test_script_path + @worker.test_script_path + end + def have_extension? - not @test_script_path.extname.empty? + not test_script_path.extname.empty? end def related_file_path(extension) - path = Pathname(@test_script_path.to_s.gsub(/\.[^.]+\z/, ".#{extension}")) - return nil if @test_script_path == path + path = Pathname(test_script_path.to_s.gsub(/\.[^.]+\z/, ".#{extension}")) + return nil if test_script_path == path path end @@ -1068,95 +1236,99 @@ EOF end end - class StreamReporter + class BaseReporter def initialize(tester) @tester = tester @term_width = guess_term_width - @current_column = 0 @output = STDOUT - @n_tests = 0 - @n_passed_tests = 0 - @n_not_checked_tests = 0 - @failed_tests = [] + reset_current_column end - def start + private + def print(message) + @current_column += message.to_s.size + @output.print(message) + @output.flush end - def start_suite(suite_name) - puts(suite_name) + def puts(*messages) + reset_current_column + @output.puts(*messages) + end + + def reset_current_column + @current_column = 0 + end + + def guess_term_width + Integer(ENV["COLUMNS"] || ENV["TERM_WIDTH"] || 79) + rescue ArgumentError + 0 + end + end + + class StreamReporter < BaseReporter + def initialize(tester) + super + end + + def start(workers) + end + + def start_worker(worker) + end + + def start_suite(worker) + puts(worker.suite_name) @output.flush end - def start_test(test_name) - @test_name = test_name - print(" #{@test_name}") + def start_test(worker) + label = " #{worker.test_name}" + print(label) @output.flush end - def pass_test(result) - report_test_result(result, "pass") - clear_line - @n_passed_tests += 1 + def pass_test(worker, result) + report_test_result(result, worker.status) + puts end - def fail_test(result) - report_test_result(result, "fail") + def fail_test(worker, result) + report_test_result(result, worker.status) puts puts("=" * @term_width) report_diff(result.expected, result.actual) puts("=" * @term_width) - @failed_tests << @test_name end - def no_check_test(result) - report_test_result(result, "not checked") + def no_check_test(worker, result) + report_test_result(result, worker.status) puts puts(result.actual) - @n_not_checked_tests += 1 - end - - def finish_test(result) - @n_tests += 1 - end - - def finish_suite(suite_name) - end - - def finish(result) - puts - puts("#{@n_tests} tests, " + - "#{@n_passed_tests} passes, " + - "#{@failed_tests.size} failures, " + - "#{@n_not_checked_tests} not checked tests.") - if @n_tests.zero? - pass_ratio = 0 - else - pass_ratio = (@n_passed_tests / @n_tests.to_f) * 100 - end - puts("%.4g%% passed in %.4fs." % [pass_ratio, result.elapsed_time]) end - private - def print(message) - @current_column += message.to_s.size - @output.print(message) - @output.flush + def finish_test(worker, result) end - def puts(*messages) - reset_current_column - @output.puts(*messages) + def finish_suite(worker) end - def reset_current_column - @current_column = 0 + def finish_worker(worker_id) end - def clear_line + def finish(result) puts + puts("#{result.n_tests} tests, " + + "#{result.n_passed_tests} passes, " + + "#{result.n_failed_tests} failures, " + + "#{result.n_not_checked_tests} not checked tests.") + pass_ratio = result.pass_ratio + elapsed_time = result.elapsed_time + puts("%.4g%% passed in %.4fs." % [pass_ratio, elapsed_time]) end + private def report_test_result(result, label) message = " %10.4fs [%s]" % [result.elapsed_time, label] message = message.rjust(@term_width - @current_column) if @term_width > 0 @@ -1180,40 +1352,97 @@ EOF file.close yield(file) end - - def guess_term_width - Integer(ENV["COLUMNS"] || ENV["TERM_WIDTH"] || 79) - rescue ArgumentError - 0 - end end - class InplaceReporter < StreamReporter - def finish_suite(suite_name) - up_n_lines(n_using_lines) - clear_line + class InplaceReporter < BaseReporter + def initialize(tester) + super + @mutex = Mutex.new + @workers = nil end - def finish(result) - n_using_lines.times do + def start(workers) + @workers = workers + n_workers.times do + puts puts end + end + + def start_worker(worker) + end + + def start_suite(worker) + redraw + end + + def start_test(worker) + redraw + end + + def pass_test(worker, result) + redraw + end + + def fail_test(worker, result) + redraw + end + + def no_check_test(worker, result) + redraw + end + + def finish_test(worker, result) + redraw + end + + def finish_suite(worker) + redraw + end + + def finish_worker(worker_id) + redraw + end + + def finish(result) puts - super + puts("#{result.n_tests} tests, " + + "#{result.n_passed_tests} passes, " + + "#{result.n_failed_tests} failures, " + + "#{result.n_not_checked_tests} not checked tests.") + pass_ratio = result.pass_ratio + elapsed_time = result.elapsed_time + puts("%.4g%% passed in %.4fs." % [pass_ratio, elapsed_time]) end private + def redraw + @mutex.synchronize do + up_n_lines(n_using_lines) + @workers.each do |worker| + clear_line + puts("[#{worker.id}] (#{worker.status}) #{worker.suite_name}") + clear_line + puts(" #{worker.test_name}") + end + end + end + def up_n_lines(n) - print("\e[1A" * n_using_lines) + print("\e[1A" * n) end def clear_line + print(" " * @term_width) print("\r") - reset_current_column end def n_using_lines - 2 + 2 * n_workers + end + + def n_workers + @tester.n_workers end end end -------------- next part -------------- HTML����������������������������... 다운로드