grntest 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/text/news.md +11 -1
- data/lib/grntest/base-result.rb +32 -0
- data/lib/grntest/error.rb +39 -0
- data/lib/grntest/execution-context.rb +89 -0
- data/lib/grntest/executors.rb +19 -0
- data/lib/grntest/executors/base-executor.rb +332 -0
- data/lib/grntest/executors/http-executor.rb +60 -0
- data/lib/grntest/executors/standard-io-executor.rb +71 -0
- data/lib/grntest/reporters.rb +37 -0
- data/lib/grntest/reporters/base-reporter.rb +375 -0
- data/lib/grntest/reporters/inplace-reporter.rb +208 -0
- data/lib/grntest/reporters/mark-reporter.rb +112 -0
- data/lib/grntest/reporters/stream-reporter.rb +86 -0
- data/lib/grntest/response-parser.rb +64 -0
- data/lib/grntest/test-runner.rb +511 -0
- data/lib/grntest/test-suites-runner.rb +141 -0
- data/lib/grntest/tester.rb +2 -1975
- data/lib/grntest/version.rb +1 -1
- data/lib/grntest/worker.rb +164 -0
- data/test/executors/test-base-executor.rb +42 -0
- data/test/executors/test-standard-io-executor.rb +61 -0
- metadata +22 -10
- data/test/test-executor.rb +0 -207
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require "open-uri"
|
19
|
+
|
20
|
+
require "grntest/executors/base-executor"
|
21
|
+
|
22
|
+
module Grntest
|
23
|
+
module Executors
|
24
|
+
class HTTPExecutor < BaseExecutor
|
25
|
+
def initialize(host, port, context=nil)
|
26
|
+
super(context)
|
27
|
+
@host = host
|
28
|
+
@port = port
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_command(command)
|
32
|
+
url = "http://#{@host}:#{@port}#{command.to_uri_format}"
|
33
|
+
begin
|
34
|
+
open(url) do |response|
|
35
|
+
"#{response.read}\n"
|
36
|
+
end
|
37
|
+
rescue OpenURI::HTTPError
|
38
|
+
message = "Failed to get response from groonga: #{$!}: <#{url}>"
|
39
|
+
raise Error.new(message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def ensure_groonga_ready
|
44
|
+
n_retried = 0
|
45
|
+
begin
|
46
|
+
send_command("status")
|
47
|
+
rescue SystemCallError
|
48
|
+
n_retried += 1
|
49
|
+
sleep(0.1)
|
50
|
+
retry if n_retried < 10
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_sub_executor(context)
|
56
|
+
self.class.new(@host, @port, context)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require "grntest/executors/base-executor"
|
19
|
+
|
20
|
+
module Grntest
|
21
|
+
module Executors
|
22
|
+
class StandardIOExecutor < BaseExecutor
|
23
|
+
def initialize(input, output, context=nil)
|
24
|
+
super(context)
|
25
|
+
@input = input
|
26
|
+
@output = output
|
27
|
+
end
|
28
|
+
|
29
|
+
def send_command(command)
|
30
|
+
command_line = @current_command.original_source
|
31
|
+
unless @current_command.has_key?(:output_type)
|
32
|
+
command_line = command_line.sub(/$/, " --output_type #{@output_type}")
|
33
|
+
end
|
34
|
+
begin
|
35
|
+
@input.print(command_line)
|
36
|
+
@input.print("\n")
|
37
|
+
@input.flush
|
38
|
+
rescue SystemCallError
|
39
|
+
message = "failed to write to groonga: <#{command_line}>: #{$!}"
|
40
|
+
raise Error.new(message)
|
41
|
+
end
|
42
|
+
read_output
|
43
|
+
end
|
44
|
+
|
45
|
+
def ensure_groonga_ready
|
46
|
+
@input.print("status\n")
|
47
|
+
@input.flush
|
48
|
+
@output.gets
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_sub_executor(context)
|
52
|
+
self.class.new(@input, @output, context)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def read_output
|
57
|
+
options = {}
|
58
|
+
options[:first_timeout] = @long_timeout if may_slow_command?
|
59
|
+
read_all_readable_content(@output, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
MAY_SLOW_COMMANDS = [
|
63
|
+
"column_create",
|
64
|
+
"register",
|
65
|
+
]
|
66
|
+
def may_slow_command?
|
67
|
+
MAY_SLOW_COMMANDS.include?(@current_command)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require "grntest/reporters/mark-reporter"
|
19
|
+
require "grntest/reporters/stream-reporter"
|
20
|
+
require "grntest/reporters/inplace-reporter"
|
21
|
+
|
22
|
+
module Grntest
|
23
|
+
module Reporters
|
24
|
+
class << self
|
25
|
+
def create_repoter(tester)
|
26
|
+
case tester.reporter
|
27
|
+
when :mark
|
28
|
+
MarkReporter.new(tester)
|
29
|
+
when :stream
|
30
|
+
StreamReporter.new(tester)
|
31
|
+
when :inplace
|
32
|
+
InplaceReporter.new(tester)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,375 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
module Grntest
|
19
|
+
module Reporters
|
20
|
+
class BaseReporter
|
21
|
+
def initialize(tester)
|
22
|
+
@tester = tester
|
23
|
+
@term_width = guess_term_width
|
24
|
+
@output = @tester.output
|
25
|
+
@mutex = Mutex.new
|
26
|
+
reset_current_column
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def synchronize
|
31
|
+
@mutex.synchronize do
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def report_summary(result)
|
37
|
+
puts(statistics_header)
|
38
|
+
puts(colorize(statistics(result), result))
|
39
|
+
pass_ratio = result.pass_ratio
|
40
|
+
elapsed_time = result.elapsed_time
|
41
|
+
summary = "%.4g%% passed in %.4fs." % [pass_ratio, elapsed_time]
|
42
|
+
puts(colorize(summary, result))
|
43
|
+
end
|
44
|
+
|
45
|
+
def columns
|
46
|
+
[
|
47
|
+
# label, format value
|
48
|
+
["tests/sec", lambda {|result| "%9.2f" % throughput(result)}],
|
49
|
+
[" tests", lambda {|result| "%8d" % result.n_tests}],
|
50
|
+
[" passes", lambda {|result| "%8d" % result.n_passed_tests}],
|
51
|
+
["failures", lambda {|result| "%8d" % result.n_failed_tests}],
|
52
|
+
[" leaked", lambda {|result| "%8d" % result.n_leaked_tests}],
|
53
|
+
[" omitted", lambda {|result| "%8d" % result.n_omitted_tests}],
|
54
|
+
["!checked", lambda {|result| "%8d" % result.n_not_checked_tests}],
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def statistics_header
|
59
|
+
labels = columns.collect do |label, format_value|
|
60
|
+
label
|
61
|
+
end
|
62
|
+
" " + labels.join(" | ") + " |"
|
63
|
+
end
|
64
|
+
|
65
|
+
def statistics(result)
|
66
|
+
items = columns.collect do |label, format_value|
|
67
|
+
format_value.call(result)
|
68
|
+
end
|
69
|
+
" " + items.join(" | ") + " |"
|
70
|
+
end
|
71
|
+
|
72
|
+
def throughput(result)
|
73
|
+
if result.elapsed_time.zero?
|
74
|
+
tests_per_second = 0
|
75
|
+
else
|
76
|
+
tests_per_second = result.n_tests / result.elapsed_time
|
77
|
+
end
|
78
|
+
tests_per_second
|
79
|
+
end
|
80
|
+
|
81
|
+
def report_failure(result)
|
82
|
+
report_marker(result)
|
83
|
+
report_diff(result.expected, result.actual)
|
84
|
+
report_marker(result)
|
85
|
+
end
|
86
|
+
|
87
|
+
def report_actual(result)
|
88
|
+
report_marker(result)
|
89
|
+
puts(result.actual)
|
90
|
+
report_marker(result)
|
91
|
+
end
|
92
|
+
|
93
|
+
def report_marker(result)
|
94
|
+
puts(colorize("=" * @term_width, result))
|
95
|
+
end
|
96
|
+
|
97
|
+
def report_diff(expected, actual)
|
98
|
+
create_temporary_file("expected", expected) do |expected_file|
|
99
|
+
create_temporary_file("actual", actual) do |actual_file|
|
100
|
+
diff_options = @tester.diff_options.dup
|
101
|
+
diff_options.concat(["--label", "(expected)", expected_file.path,
|
102
|
+
"--label", "(actual)", actual_file.path])
|
103
|
+
system(@tester.diff, *diff_options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def report_test(worker, result)
|
109
|
+
report_marker(result)
|
110
|
+
print("[#{worker.id}] ") if @tester.n_workers > 1
|
111
|
+
puts(worker.suite_name)
|
112
|
+
print(" #{worker.test_name}")
|
113
|
+
report_test_result(result, worker.status)
|
114
|
+
end
|
115
|
+
|
116
|
+
def report_test_result(result, label)
|
117
|
+
message = test_result_message(result, label)
|
118
|
+
message_width = string_width(message)
|
119
|
+
rest_width = @term_width - @current_column
|
120
|
+
if rest_width > message_width
|
121
|
+
print(" " * (rest_width - message_width))
|
122
|
+
end
|
123
|
+
puts(message)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_result_message(result, label)
|
127
|
+
elapsed_time = result.elapsed_time
|
128
|
+
formatted_elapsed_time = "%.4fs" % elapsed_time
|
129
|
+
formatted_elapsed_time = colorize(formatted_elapsed_time,
|
130
|
+
elapsed_time_status(elapsed_time))
|
131
|
+
" #{formatted_elapsed_time} [#{colorize(label, result)}]"
|
132
|
+
end
|
133
|
+
|
134
|
+
LONG_ELAPSED_TIME = 1.0
|
135
|
+
def long_elapsed_time?(elapsed_time)
|
136
|
+
elapsed_time >= LONG_ELAPSED_TIME
|
137
|
+
end
|
138
|
+
|
139
|
+
def elapsed_time_status(elapsed_time)
|
140
|
+
if long_elapsed_time?(elapsed_time)
|
141
|
+
elapsed_time_status = :failure
|
142
|
+
else
|
143
|
+
elapsed_time_status = :not_checked
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def justify(message, width)
|
148
|
+
return " " * width if message.nil?
|
149
|
+
return message.ljust(width) if message.bytesize <= width
|
150
|
+
half_width = width / 2.0
|
151
|
+
elision_mark = "..."
|
152
|
+
left = message[0, half_width.ceil - elision_mark.size]
|
153
|
+
right = message[(message.size - half_width.floor)..-1]
|
154
|
+
"#{left}#{elision_mark}#{right}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def print(message)
|
158
|
+
@current_column += string_width(message.to_s)
|
159
|
+
@output.print(message)
|
160
|
+
end
|
161
|
+
|
162
|
+
def puts(*messages)
|
163
|
+
reset_current_column
|
164
|
+
@output.puts(*messages)
|
165
|
+
end
|
166
|
+
|
167
|
+
def reset_current_column
|
168
|
+
@current_column = 0
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_temporary_file(key, content)
|
172
|
+
file = Tempfile.new("groonga-test-#{key}")
|
173
|
+
file.print(content)
|
174
|
+
file.close
|
175
|
+
yield(file)
|
176
|
+
end
|
177
|
+
|
178
|
+
def guess_term_width
|
179
|
+
Integer(guess_term_width_from_env || guess_term_width_from_stty || 79)
|
180
|
+
rescue ArgumentError
|
181
|
+
0
|
182
|
+
end
|
183
|
+
|
184
|
+
def guess_term_width_from_env
|
185
|
+
ENV["COLUMNS"] || ENV["TERM_WIDTH"]
|
186
|
+
end
|
187
|
+
|
188
|
+
def guess_term_width_from_stty
|
189
|
+
return nil unless STDIN.tty?
|
190
|
+
|
191
|
+
case tty_info
|
192
|
+
when /(\d+) columns/
|
193
|
+
$1
|
194
|
+
when /columns (\d+)/
|
195
|
+
$1
|
196
|
+
else
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def tty_info
|
202
|
+
begin
|
203
|
+
`stty -a`
|
204
|
+
rescue SystemCallError
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def string_width(string)
|
210
|
+
string.gsub(/\e\[[0-9;]+m/, "").size
|
211
|
+
end
|
212
|
+
|
213
|
+
def result_status(result)
|
214
|
+
if result.respond_to?(:status)
|
215
|
+
result.status
|
216
|
+
else
|
217
|
+
if result.n_failed_tests > 0
|
218
|
+
:failure
|
219
|
+
elsif result.n_leaked_tests > 0
|
220
|
+
:leaked
|
221
|
+
elsif result.n_omitted_tests > 0
|
222
|
+
:omitted
|
223
|
+
elsif result.n_not_checked_tests > 0
|
224
|
+
:not_checked
|
225
|
+
else
|
226
|
+
:success
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def colorize(message, result_or_status)
|
232
|
+
return message unless @tester.use_color?
|
233
|
+
if result_or_status.is_a?(Symbol)
|
234
|
+
status = result_or_status
|
235
|
+
else
|
236
|
+
status = result_status(result_or_status)
|
237
|
+
end
|
238
|
+
case status
|
239
|
+
when :success
|
240
|
+
"%s%s%s" % [success_color, message, reset_color]
|
241
|
+
when :failure
|
242
|
+
"%s%s%s" % [failure_color, message, reset_color]
|
243
|
+
when :leaked
|
244
|
+
"%s%s%s" % [leaked_color, message, reset_color]
|
245
|
+
when :omitted
|
246
|
+
"%s%s%s" % [omitted_color, message, reset_color]
|
247
|
+
when :not_checked
|
248
|
+
"%s%s%s" % [not_checked_color, message, reset_color]
|
249
|
+
else
|
250
|
+
message
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def success_color
|
255
|
+
escape_sequence({
|
256
|
+
:color => :green,
|
257
|
+
:color_256 => [0, 3, 0],
|
258
|
+
:background => true,
|
259
|
+
},
|
260
|
+
{
|
261
|
+
:color => :white,
|
262
|
+
:color_256 => [5, 5, 5],
|
263
|
+
:bold => true,
|
264
|
+
})
|
265
|
+
end
|
266
|
+
|
267
|
+
def failure_color
|
268
|
+
escape_sequence({
|
269
|
+
:color => :red,
|
270
|
+
:color_256 => [3, 0, 0],
|
271
|
+
:background => true,
|
272
|
+
},
|
273
|
+
{
|
274
|
+
:color => :white,
|
275
|
+
:color_256 => [5, 5, 5],
|
276
|
+
:bold => true,
|
277
|
+
})
|
278
|
+
end
|
279
|
+
|
280
|
+
def leaked_color
|
281
|
+
escape_sequence({
|
282
|
+
:color => :magenta,
|
283
|
+
:color_256 => [3, 0, 3],
|
284
|
+
:background => true,
|
285
|
+
},
|
286
|
+
{
|
287
|
+
:color => :white,
|
288
|
+
:color_256 => [5, 5, 5],
|
289
|
+
:bold => true,
|
290
|
+
})
|
291
|
+
end
|
292
|
+
|
293
|
+
def omitted_color
|
294
|
+
escape_sequence({
|
295
|
+
:color => :blue,
|
296
|
+
:color_256 => [0, 0, 1],
|
297
|
+
:background => true,
|
298
|
+
},
|
299
|
+
{
|
300
|
+
:color => :white,
|
301
|
+
:color_256 => [5, 5, 5],
|
302
|
+
:bold => true,
|
303
|
+
})
|
304
|
+
end
|
305
|
+
|
306
|
+
def not_checked_color
|
307
|
+
escape_sequence({
|
308
|
+
:color => :cyan,
|
309
|
+
:color_256 => [0, 1, 1],
|
310
|
+
:background => true,
|
311
|
+
},
|
312
|
+
{
|
313
|
+
:color => :white,
|
314
|
+
:color_256 => [5, 5, 5],
|
315
|
+
:bold => true,
|
316
|
+
})
|
317
|
+
end
|
318
|
+
|
319
|
+
def reset_color
|
320
|
+
escape_sequence(:reset)
|
321
|
+
end
|
322
|
+
|
323
|
+
COLOR_NAMES = [
|
324
|
+
:black, :red, :green, :yellow,
|
325
|
+
:blue, :magenta, :cyan, :white,
|
326
|
+
]
|
327
|
+
def escape_sequence(*commands)
|
328
|
+
sequence = []
|
329
|
+
commands.each do |command|
|
330
|
+
case command
|
331
|
+
when :reset
|
332
|
+
sequence << "0"
|
333
|
+
when :bold
|
334
|
+
sequence << "1"
|
335
|
+
when :italic
|
336
|
+
sequence << "3"
|
337
|
+
when :underline
|
338
|
+
sequence << "4"
|
339
|
+
when Hash
|
340
|
+
foreground_p = !command[:background]
|
341
|
+
if available_colors == 256
|
342
|
+
sequence << (foreground_p ? "38" : "48")
|
343
|
+
sequence << "5"
|
344
|
+
sequence << pack_256_color(*command[:color_256])
|
345
|
+
else
|
346
|
+
color_parameter = foreground_p ? 3 : 4
|
347
|
+
color_parameter += 6 if command[:intensity]
|
348
|
+
color = COLOR_NAMES.index(command[:color])
|
349
|
+
sequence << "#{color_parameter}#{color}"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
"\e[#{sequence.join(';')}m"
|
354
|
+
end
|
355
|
+
|
356
|
+
def pack_256_color(red, green, blue)
|
357
|
+
red * 36 + green * 6 + blue + 16
|
358
|
+
end
|
359
|
+
|
360
|
+
def available_colors
|
361
|
+
case ENV["COLORTERM"]
|
362
|
+
when "gnome-terminal"
|
363
|
+
256
|
364
|
+
else
|
365
|
+
case ENV["TERM"]
|
366
|
+
when /-256color\z/
|
367
|
+
256
|
368
|
+
else
|
369
|
+
8
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|