lib/grntest/tester.rb in grntest-1.0.0 vs lib/grntest/tester.rb in grntest-1.0.1
- old
+ new
@@ -120,10 +120,17 @@
"Use DIRECTORY as a base directory of relative path",
"(#{tester.base_directory})") do |directory|
tester.base_directory = Pathname(directory)
end
+ parser.on("--database=PATH",
+ "Use existing database at PATH " +
+ "instead of creating a new database",
+ "(creating a new database)") do |path|
+ tester.database_path = path
+ end
+
parser.on("--diff=DIFF",
"Use DIFF as diff command",
"(#{tester.diff})") do |diff|
tester.diff = diff
tester.diff_options.clear
@@ -222,11 +229,11 @@
end
end
attr_accessor :groonga, :groonga_httpd, :groonga_suggest_create_dataset
attr_accessor :interface, :output_type, :testee
- attr_accessor :base_directory, :diff, :diff_options
+ attr_accessor :base_directory, :database_path, :diff, :diff_options
attr_accessor :n_workers
attr_accessor :output
attr_accessor :gdb, :default_gdb
attr_writer :reporter, :keep_database, :use_color
attr_reader :test_patterns, :test_suite_patterns
@@ -237,10 +244,11 @@
@groonga_suggest_create_dataset = "groonga-suggest-create-dataset"
@interface = :stdio
@output_type = "json"
@testee = "groonga"
@base_directory = Pathname(".")
+ @database_path = nil
@reporter = nil
@n_workers = 1
@output = $stdout
@keep_database = false
@use_color = nil
@@ -286,11 +294,12 @@
def target_test?(test_name)
selected_test?(test_name) and not excluded_test?(test_name)
end
def selected_test?(test_name)
- @test_patterns.all? do |pattern|
+ return true if @test_patterns.empty?
+ @test_patterns.any? do |pattern|
pattern === test_name
end
end
def excluded_test?(test_name)
@@ -303,11 +312,12 @@
selected_test_suite?(test_suite_name) and
not excluded_test_suite?(test_suite_name)
end
def selected_test_suite?(test_suite_name)
- @test_suite_patterns.all? do |pattern|
+ return true if @test_suite_patterns.empty?
+ @test_suite_patterns.any? do |pattern|
pattern === test_suite_name
end
end
def excluded_test_suite?(test_suite_name)
@@ -394,16 +404,18 @@
@elapsed_time = Time.now - start_time
end
end
class WorkerResult < Result
- attr_reader :n_tests, :n_passed_tests, :n_not_checked_tests
+ attr_reader :n_tests, :n_passed_tests, :n_leaked_tests
+ attr_reader :n_not_checked_tests
attr_reader :failed_tests
def initialize
super
@n_tests = 0
@n_passed_tests = 0
+ @n_leaked_tests = 0
@n_not_checked_tests = 0
@failed_tests = []
end
def n_failed_tests
@@ -420,10 +432,14 @@
def test_failed(name)
@failed_tests << name
end
+ def test_leaked(name)
+ @n_leaked_tests += 1
+ end
+
def test_not_checked
@n_not_checked_tests += 1
end
end
@@ -499,14 +515,20 @@
@status = "failed"
@result.test_failed(test_name)
@reporter.fail_test(self, result)
end
- def not_check_test(result)
+ def leaked_test(result)
+ @status = "leaked(#{result.n_leaked_objects})"
+ @result.test_leaked(test_name)
+ @reporter.leaked_test(self, result)
+ end
+
+ def not_checked_test(result)
@status = "not checked"
@result.test_not_checked
- @reporter.not_check_test(self, result)
+ @reporter.not_checked_test(self, result)
end
def finish_test(result)
@result.test_finished
@reporter.finish_test(self, result)
@@ -543,10 +565,14 @@
def n_failed_tests
collect_count(:n_failed_tests)
end
+ def n_leaked_tests
+ collect_count(:n_leaked_tests)
+ end
+
def n_not_checked_tests
collect_count(:n_not_checked_tests)
end
private
@@ -631,28 +657,37 @@
end
end
class TestResult < Result
attr_accessor :worker_id, :test_name
- attr_accessor :expected, :actual
+ attr_accessor :expected, :actual, :n_leaked_objects
def initialize(worker)
super()
@worker_id = worker.id
@test_name = worker.test_name
@actual = nil
@expected = nil
+ @n_leaked_objects = 0
end
def status
if @expected
if @actual == @expected
- :success
+ if @n_leaked_objects.zero?
+ :success
+ else
+ :leaked
+ end
else
:failure
end
else
- :not_checked
+ if @n_leaked_objects.zero?
+ :not_checked
+ else
+ :leaked
+ end
end
end
end
class TestRunner
@@ -671,45 +706,53 @@
@worker.start_test
result = TestResult.new(@worker)
result.measure do
result.actual = execute_groonga_script
end
- result.actual = normalize_result(result.actual)
+ normalize_actual_result(result)
result.expected = read_expected_result
case result.status
when :success
@worker.pass_test(result)
remove_reject_file
when :failure
@worker.fail_test(result)
output_reject_file(result.actual)
succeeded = false
+ when :leaked
+ @worker.leaked_test(result)
+ succeeded = false
else
- @worker.not_check_test(result)
+ @worker.not_checked_test(result)
output_actual_file(result.actual)
end
@worker.finish_test(result)
succeeded
end
private
def execute_groonga_script
create_temporary_directory do |directory_path|
- db_dir = directory_path + "db"
- FileUtils.mkdir_p(db_dir.to_s)
- db_path = db_dir + "db"
+ if @tester.database_path
+ db_path = Pathname(@tester.database_path).expand_path
+ else
+ db_dir = directory_path + "db"
+ FileUtils.mkdir_p(db_dir.to_s)
+ db_path = db_dir + "db"
+ end
context = Executor::Context.new
context.temporary_directory_path = directory_path
context.db_path = db_path
context.base_directory = @tester.base_directory.expand_path
context.groonga_suggest_create_dataset =
@tester.groonga_suggest_create_dataset
context.output_type = @tester.output_type
run_groonga(context) do |executor|
executor.execute(test_script_path)
end
+ check_memory_leak(context)
context.result
end
end
def create_temporary_directory
@@ -732,10 +775,14 @@
def keep_database_path
test_script_path.to_s.gsub(/\//, ".")
end
def run_groonga(context, &block)
+ unless @tester.database_path
+ create_empty_database(context.db_path.to_s)
+ end
+
case @tester.interface
when :stdio
run_groonga_stdio(context, &block)
when :http
run_groonga_http(context, &block)
@@ -758,11 +805,10 @@
}
command_line = groonga_command_line(context, spawn_options)
command_line += [
"--input-fd", input_fd.to_s,
"--output-fd", output_fd.to_s,
- "-n",
context.relative_db_path.to_s,
]
pid = Process.spawn(env, *command_line, spawn_options)
executor = StandardIOExecutor.new(groonga_input,
groonga_output,
@@ -888,11 +934,10 @@
"--pid-path", pid_file_path.to_s,
"--bind-address", host,
"--port", port.to_s,
"--protocol", "http",
"-s",
- "-n",
context.relative_db_path.to_s,
]
when "groonga-httpd"
command_line = command_command_line(@tester.groonga_httpd, context,
spawn_options)
@@ -905,11 +950,10 @@
end
command_line
end
def create_config_file(context, host, port, pid_file_path)
- create_empty_database(context.db_path.to_s)
config_file_path =
context.temporary_directory_path + "groonga-httpd.conf"
config_file_path.open("w") do |config_file|
config_file.puts(<<EOF)
daemon off;
@@ -948,23 +992,25 @@
]
system(*create_database_command)
output_fd.close(true)
end
- def normalize_result(result)
+ def normalize_actual_result(result)
normalized_result = ""
- result.each do |tag, content, options|
+ result.actual.each do |tag, content, options|
case tag
when :input
normalized_result << content
when :output
normalized_result << normalize_output(content, options)
when :error
normalized_result << normalize_raw_content(content)
+ when :n_leaked_objects
+ result.n_leaked_objects = content
end
end
- normalized_result
+ result.actual = normalized_result
end
def normalize_raw_content(content)
"#{content}\n".force_encoding("ASCII-8BIT")
end
@@ -1067,10 +1113,21 @@
return if result_path.nil?
result_path.open("w:ascii-8bit") do |result_file|
result_file.print(actual_result)
end
end
+
+ def check_memory_leak(context)
+ context.log.each_line do |line|
+ timestamp, log_level, message = line.split(/\|\s*/, 3)
+ _ = timestamp # suppress warning
+ next unless /^grn_fin \((\d+)\)$/ =~ message
+ n_leaked_objects = $1.to_i
+ next if n_leaked_objects.zero?
+ context.result << [:n_leaked_objects, n_leaked_objects, {}]
+ end
+ end
end
class Executor
class Context
attr_writer :logging
@@ -1179,35 +1236,83 @@
end
end
def execute_line(line)
case line
+ when /\A\#@/
+ directive_content = $POSTMATCH
+ execute_directive(line, directive_content)
when /\A\s*\z/
# do nothing
when /\A\s*\#/
- comment_content = $POSTMATCH
- execute_comment(comment_content)
+ # ignore comment
else
execute_command_line(line)
end
end
- def execute_comment(content)
+ def resolve_path(path)
+ if path.relative?
+ @context.base_directory + path
+ else
+ path
+ end
+ end
+
+ def execute_directive_suggest_create_dataset(line, content, options)
+ dataset_name = options.first
+ if dataset_name.nil?
+ log_input(line)
+ log_error("#|e| [suggest-create-dataset] dataset name is missing")
+ return
+ end
+ execute_suggest_create_dataset(dataset_name)
+ end
+
+ def execute_directive_include(line, content, options)
+ path = options.first
+ if path.nil?
+ log_input(line)
+ log_error("#|e| [include] path is missing")
+ return
+ end
+ execute_script(Pathname(path))
+ end
+
+ def execute_directive_copy_path(line, content, options)
+ source, destination, = options
+ if source.nil? or destination.nil?
+ log_input(line)
+ if source.nil?
+ log_error("#|e| [copy-path] source is missing")
+ end
+ if destiantion.nil?
+ log_error("#|e| [copy-path] destination is missing")
+ end
+ return
+ end
+ source = resolve_path(Pathname(source))
+ destination = resolve_path(Pathname(destination))
+ FileUtils.cp_r(source.to_s, destination.to_s)
+ end
+
+ def execute_directive(line, content)
command, *options = Shellwords.split(content)
case command
when "disable-logging"
@context.logging = false
when "enable-logging"
@context.logging = true
when "suggest-create-dataset"
- dataset_name = options.first
- return if dataset_name.nil?
- execute_suggest_create_dataset(dataset_name)
+ execute_directive_suggest_create_dataset(line, content, options)
when "include"
- path = options.first
- return if path.nil?
- execute_script(Pathname(path))
+ execute_directive_include(line, content, options)
+ when "copy-path"
+ execute_directive_copy_path(line, content, options)
+ else
+ log_input(line)
+ log_error("#|e| unknown directive: <#{command}>")
end
end
def execute_suggest_create_dataset(dataset_name)
command_line = [@context.groonga_suggest_create_dataset,
@@ -1225,14 +1330,11 @@
end
end
def execute_script(script_path)
executor = create_sub_executor(@context)
- if script_path.relative?
- script_path = @context.base_directory + script_path
- end
- executor.execute(script_path)
+ executor.execute(resolve_path(script_path))
end
def execute_command_line(command_line)
extract_command_info(command_line)
log_input(command_line)
@@ -1511,34 +1613,49 @@
yield
end
end
def report_summary(result)
+ puts(statistics_header)
puts(colorize(statistics(result), result))
pass_ratio = result.pass_ratio
elapsed_time = result.elapsed_time
summary = "%.4g%% passed in %.4fs." % [pass_ratio, elapsed_time]
puts(colorize(summary, result))
end
+ def statistics_header
+ items = [
+ "tests/sec",
+ "tests",
+ "passes",
+ "failures",
+ "leaked",
+ "!checked",
+ ]
+ " " + ((["%-9s"] * items.size).join(" | ") % items) + " |"
+ end
+
def statistics(result)
items = [
- "#{result.n_tests} tests",
- "#{result.n_passed_tests} passes",
- "#{result.n_failed_tests} failures",
- "#{result.n_not_checked_tests} not checked_tests",
+ "%9.2f" % throughput(result),
+ "%9d" % result.n_tests,
+ "%9d" % result.n_passed_tests,
+ "%9d" % result.n_failed_tests,
+ "%9d" % result.n_leaked_tests,
+ "%9d" % result.n_not_checked_tests,
]
- "#{throughput(result)}: " + items.join(", ")
+ " " + items.join(" | ") + " |"
end
def throughput(result)
if result.elapsed_time.zero?
tests_per_second = 0
else
tests_per_second = result.n_tests / result.elapsed_time
end
- "%.2f tests/sec" % tests_per_second
+ tests_per_second
end
def report_failure(result)
report_marker(result)
report_diff(result.expected, result.actual)
@@ -1557,12 +1674,12 @@
def report_diff(expected, actual)
create_temporary_file("expected", expected) do |expected_file|
create_temporary_file("actual", actual) do |actual_file|
diff_options = @tester.diff_options.dup
- diff_options.concat(["--label", "(actual)", actual_file.path,
- "--label", "(expected)", expected_file.path])
+ diff_options.concat(["--label", "(expected)", expected_file.path,
+ "--label", "(actual)", actual_file.path])
system(@tester.diff, *diff_options)
end
end
end
@@ -1667,10 +1784,12 @@
if result.respond_to?(:status)
result.status
else
if result.n_failed_tests > 0
:failure
+ elsif result.n_leaked_tests > 0
+ :leaked
elsif result.n_not_checked_tests > 0
:not_checked
else
:success
end
@@ -1687,10 +1806,12 @@
case status
when :success
"%s%s%s" % [success_color, message, reset_color]
when :failure
"%s%s%s" % [failure_color, message, reset_color]
+ when :leaked
+ "%s%s%s" % [leaked_color, message, reset_color]
when :not_checked
"%s%s%s" % [not_checked_color, message, reset_color]
else
message
end
@@ -1720,10 +1841,23 @@
:color_256 => [5, 5, 5],
:bold => true,
})
end
+ def leaked_color
+ escape_sequence({
+ :color => :magenta,
+ :color_256 => [3, 0, 3],
+ :background => true,
+ },
+ {
+ :color => :white,
+ :color_256 => [5, 5, 5],
+ :bold => true,
+ })
+ end
+
def not_checked_color
escape_sequence({
:color => :cyan,
:color_256 => [0, 1, 1],
:background => true,
@@ -1821,12 +1955,18 @@
report_test(worker, result)
report_failure(result)
end
end
- def not_check_test(worker, result)
+ def leaked_test(worker, result)
synchronize do
+ report_test_result_mark("L(#{result.n_leaked_objects})", result)
+ end
+ end
+
+ def not_checked_test(worker, result)
+ synchronize do
report_test_result_mark("N", result)
puts
report_test(worker, result)
report_actual(result)
end
@@ -1847,10 +1987,13 @@
report_summary(result)
end
private
def report_test_result_mark(mark, result)
+ if @term_width < @current_column + mark.bytesize
+ puts
+ end
print(colorize(mark, result))
if @term_width <= @current_column
puts
else
@output.flush
@@ -1890,12 +2033,16 @@
def fail_test(worker, result)
report_test_result(result, worker.status)
report_failure(result)
end
- def not_check_test(worker, result)
+ def leaked_test(worker, result)
report_test_result(result, worker.status)
+ end
+
+ def not_checked_test(worker, result)
+ report_test_result(result, worker.status)
report_actual(result)
end
def finish_test(worker, result)
end
@@ -1943,13 +2090,20 @@
report_test(worker, result)
report_failure(result)
end
end
- def not_check_test(worker, result)
+ def leaked_test(worker, result)
redraw do
report_test(worker, result)
+ report_marker(result)
+ end
+ end
+
+ def not_checked_test(worker, result)
+ redraw do
+ report_test(worker, result)
report_actual(result)
end
end
def finish_test(worker, result)
@@ -1970,17 +2124,22 @@
report_summary(result)
end
private
def draw
+ draw_statistics_header_line
@test_suites_result.workers.each do |worker|
draw_status_line(worker)
draw_test_line(worker)
end
draw_progress_line
end
+ def draw_statistics_header_line
+ puts(statistics_header)
+ end
+
def draw_status_line(worker)
clear_line
left = "[#{colorize(worker.id, worker.result)}] "
right = " [#{worker.status}]"
rest_width = @term_width - @current_column
@@ -1992,19 +2151,23 @@
def draw_test_line(worker)
clear_line
if worker.test_name
label = " #{worker.test_name}"
else
- label = " #{statistics(worker.result)}"
+ label = statistics(worker.result)
end
puts(justify(label, @term_width))
end
def draw_progress_line
n_done_tests = @test_suites_result.n_tests
n_total_tests = @test_suites_result.n_total_tests
- finished_test_ratio = n_done_tests.to_f / n_total_tests
+ if n_total_tests.zero?
+ finished_test_ratio = 0.0
+ else
+ finished_test_ratio = n_done_tests.to_f / n_total_tests
+ end
start_mark = "|"
finish_mark = "|"
statistics = " [%3d%%]" % (finished_test_ratio * 100)
@@ -2052,10 +2215,14 @@
print("\r")
reset_current_column
end
def n_using_lines
- n_worker_lines * n_workers + n_progress_lines
+ n_statistics_header_line + n_worker_lines * n_workers + n_progress_lines
+ end
+
+ def n_statistics_header_line
+ 1
end
def n_worker_lines
2
end