grntest 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|