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.
@@ -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