grntest 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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