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
data/doc/text/news.md
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 1.0.3: 2013-08-12
|
4
|
+
|
5
|
+
This is a minor improvment release.
|
6
|
+
|
7
|
+
### Improvements
|
8
|
+
|
9
|
+
* Supported XML output.
|
10
|
+
* Supported to show the actual result on leaked and not checked status.
|
11
|
+
* Supported warning message test.
|
12
|
+
|
3
13
|
## 1.0.2: 2012-12-11
|
4
14
|
|
5
|
-
This is the release that
|
15
|
+
This is the release that adds some directive.
|
6
16
|
|
7
17
|
### Improvements
|
8
18
|
|
@@ -0,0 +1,32 @@
|
|
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
|
+
class BaseResult
|
20
|
+
attr_accessor :elapsed_time
|
21
|
+
def initialize
|
22
|
+
@elapsed_time = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def measure
|
26
|
+
start_time = Time.now
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
@elapsed_time = Time.now - start_time
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
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
|
+
class Error < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class NotExist < Error
|
23
|
+
attr_reader :path
|
24
|
+
def initialize(path)
|
25
|
+
@path = path
|
26
|
+
super("<#{path}> doesn't exist.")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ParseError < Error
|
31
|
+
attr_reader :type, :content, :reason
|
32
|
+
def initialize(type, content, reason)
|
33
|
+
@type = type
|
34
|
+
@content = content
|
35
|
+
@reason = reason
|
36
|
+
super("failed to parse <#{@type}> content: #{reason}: <#{content}>")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,89 @@
|
|
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
|
+
class ExecutionContext
|
20
|
+
attr_writer :logging
|
21
|
+
attr_accessor :base_directory, :temporary_directory_path, :db_path
|
22
|
+
attr_accessor :groonga_suggest_create_dataset
|
23
|
+
attr_accessor :result
|
24
|
+
attr_accessor :output_type
|
25
|
+
attr_accessor :on_error
|
26
|
+
attr_accessor :abort_tag
|
27
|
+
def initialize
|
28
|
+
@logging = true
|
29
|
+
@base_directory = Pathname(".")
|
30
|
+
@temporary_directory_path = Pathname("tmp")
|
31
|
+
@db_path = Pathname("db")
|
32
|
+
@groonga_suggest_create_dataset = "groonga-suggest-create-dataset"
|
33
|
+
@n_nested = 0
|
34
|
+
@result = []
|
35
|
+
@output_type = "json"
|
36
|
+
@log = nil
|
37
|
+
@on_error = :default
|
38
|
+
@abort_tag = nil
|
39
|
+
@omitted = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def logging?
|
43
|
+
@logging
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
@n_nested += 1
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
@n_nested -= 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def top_level?
|
54
|
+
@n_nested == 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def log_path
|
58
|
+
@temporary_directory_path + "groonga.log"
|
59
|
+
end
|
60
|
+
|
61
|
+
def log
|
62
|
+
@log ||= File.open(log_path.to_s, "a+")
|
63
|
+
end
|
64
|
+
|
65
|
+
def relative_db_path
|
66
|
+
@db_path.relative_path_from(@temporary_directory_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
def omitted?
|
70
|
+
@omitted
|
71
|
+
end
|
72
|
+
|
73
|
+
def error
|
74
|
+
case @on_error
|
75
|
+
when :omit
|
76
|
+
omit
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def omit
|
81
|
+
@omitted = true
|
82
|
+
abort
|
83
|
+
end
|
84
|
+
|
85
|
+
def abort
|
86
|
+
throw @abort_tag
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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/standard-io-executor"
|
19
|
+
require "grntest/executors/http-executor"
|
@@ -0,0 +1,332 @@
|
|
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 "pathname"
|
19
|
+
require "fileutils"
|
20
|
+
require "shellwords"
|
21
|
+
|
22
|
+
require "groonga/command"
|
23
|
+
|
24
|
+
require "grntest/error"
|
25
|
+
require "grntest/execution-context"
|
26
|
+
require "grntest/response-parser"
|
27
|
+
|
28
|
+
module Grntest
|
29
|
+
module Executors
|
30
|
+
class BaseExecutor
|
31
|
+
module ReturnCode
|
32
|
+
SUCCESS = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :context
|
36
|
+
def initialize(context=nil)
|
37
|
+
@loading = false
|
38
|
+
@pending_command = ""
|
39
|
+
@pending_load_command = nil
|
40
|
+
@current_command_name = nil
|
41
|
+
@output_type = nil
|
42
|
+
@long_timeout = default_long_timeout
|
43
|
+
@context = context || ExecutionContext.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(script_path)
|
47
|
+
unless script_path.exist?
|
48
|
+
raise NotExist.new(script_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
@context.execute do
|
52
|
+
script_path.open("r:ascii-8bit") do |script_file|
|
53
|
+
parser = create_parser
|
54
|
+
script_file.each_line do |line|
|
55
|
+
begin
|
56
|
+
parser << line
|
57
|
+
rescue Error, Groonga::Command::ParseError
|
58
|
+
line_info = "#{script_path}:#{script_file.lineno}:#{line.chomp}"
|
59
|
+
log_error("#{line_info}: #{$!.message}")
|
60
|
+
if $!.is_a?(Groonga::Command::ParseError)
|
61
|
+
@context.abort
|
62
|
+
else
|
63
|
+
log_error("#{line_info}: #{$!.message}")
|
64
|
+
raise unless @context.top_level?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@context.result
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def create_parser
|
76
|
+
parser = Groonga::Command::Parser.new
|
77
|
+
parser.on_command do |command|
|
78
|
+
execute_command(command)
|
79
|
+
end
|
80
|
+
parser.on_load_complete do |command|
|
81
|
+
execute_command(command)
|
82
|
+
end
|
83
|
+
parser.on_comment do |comment|
|
84
|
+
if /\A@/ =~ comment
|
85
|
+
directive_content = $POSTMATCH
|
86
|
+
execute_directive("\##{comment}", directive_content)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
parser
|
90
|
+
end
|
91
|
+
|
92
|
+
def resolve_path(path)
|
93
|
+
if path.relative?
|
94
|
+
@context.base_directory + path
|
95
|
+
else
|
96
|
+
path
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def execute_directive_suggest_create_dataset(line, content, options)
|
101
|
+
dataset_name = options.first
|
102
|
+
if dataset_name.nil?
|
103
|
+
log_input(line)
|
104
|
+
log_error("#|e| [suggest-create-dataset] dataset name is missing")
|
105
|
+
return
|
106
|
+
end
|
107
|
+
execute_suggest_create_dataset(dataset_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
def execute_directive_include(line, content, options)
|
111
|
+
path = options.first
|
112
|
+
if path.nil?
|
113
|
+
log_input(line)
|
114
|
+
log_error("#|e| [include] path is missing")
|
115
|
+
return
|
116
|
+
end
|
117
|
+
execute_script(Pathname(path))
|
118
|
+
end
|
119
|
+
|
120
|
+
def execute_directive_copy_path(line, content, options)
|
121
|
+
source, destination, = options
|
122
|
+
if source.nil? or destination.nil?
|
123
|
+
log_input(line)
|
124
|
+
if source.nil?
|
125
|
+
log_error("#|e| [copy-path] source is missing")
|
126
|
+
end
|
127
|
+
if destiantion.nil?
|
128
|
+
log_error("#|e| [copy-path] destination is missing")
|
129
|
+
end
|
130
|
+
return
|
131
|
+
end
|
132
|
+
source = resolve_path(Pathname(source))
|
133
|
+
destination = resolve_path(Pathname(destination))
|
134
|
+
FileUtils.cp_r(source.to_s, destination.to_s)
|
135
|
+
end
|
136
|
+
|
137
|
+
def execute_directive_long_timeout(line, content, options)
|
138
|
+
long_timeout, = options
|
139
|
+
invalid_value_p = false
|
140
|
+
case long_timeout
|
141
|
+
when "default"
|
142
|
+
@long_timeout = default_long_timeout
|
143
|
+
when nil
|
144
|
+
invalid_value_p = true
|
145
|
+
else
|
146
|
+
begin
|
147
|
+
@long_timeout = Float(long_timeout)
|
148
|
+
rescue ArgumentError
|
149
|
+
invalid_value_p = true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if invalid_value_p
|
154
|
+
log_input(line)
|
155
|
+
message = "long-timeout must be number or 'default': <#{long_timeout}>"
|
156
|
+
log_error("#|e| [long-timeout] #{message}")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def execute_directive_on_error(line, content, options)
|
161
|
+
action, = options
|
162
|
+
invalid_value_p = false
|
163
|
+
valid_actions = ["default", "omit"]
|
164
|
+
if valid_actions.include?(action)
|
165
|
+
@context.on_error = action.to_sym
|
166
|
+
else
|
167
|
+
invalid_value_p = true
|
168
|
+
end
|
169
|
+
|
170
|
+
if invalid_value_p
|
171
|
+
log_input(line)
|
172
|
+
valid_actions_label = "[#{valid_actions.join(', ')}]"
|
173
|
+
message = "on-error must be one of #{valid_actions_label}"
|
174
|
+
log_error("#|e| [on-error] #{message}: <#{action}>")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def execute_directive_omit(line, content, options)
|
179
|
+
reason, = options
|
180
|
+
@output_type = "raw"
|
181
|
+
log_output("omit: #{reason}")
|
182
|
+
@context.omit
|
183
|
+
end
|
184
|
+
|
185
|
+
def execute_directive(line, content)
|
186
|
+
command, *options = Shellwords.split(content)
|
187
|
+
case command
|
188
|
+
when "disable-logging"
|
189
|
+
@context.logging = false
|
190
|
+
when "enable-logging"
|
191
|
+
@context.logging = true
|
192
|
+
when "suggest-create-dataset"
|
193
|
+
execute_directive_suggest_create_dataset(line, content, options)
|
194
|
+
when "include"
|
195
|
+
execute_directive_include(line, content, options)
|
196
|
+
when "copy-path"
|
197
|
+
execute_directive_copy_path(line, content, options)
|
198
|
+
when "long-timeout"
|
199
|
+
execute_directive_long_timeout(line, content, options)
|
200
|
+
when "on-error"
|
201
|
+
execute_directive_on_error(line, content, options)
|
202
|
+
when "omit"
|
203
|
+
execute_directive_omit(line, content, options)
|
204
|
+
else
|
205
|
+
log_input(line)
|
206
|
+
log_error("#|e| unknown directive: <#{command}>")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def execute_suggest_create_dataset(dataset_name)
|
211
|
+
command_line = [@context.groonga_suggest_create_dataset,
|
212
|
+
@context.db_path.to_s,
|
213
|
+
dataset_name]
|
214
|
+
packed_command_line = command_line.join(" ")
|
215
|
+
log_input("#{packed_command_line}\n")
|
216
|
+
begin
|
217
|
+
IO.popen(command_line, "r:ascii-8bit") do |io|
|
218
|
+
log_output(io.read)
|
219
|
+
end
|
220
|
+
rescue SystemCallError
|
221
|
+
raise Error.new("failed to run groonga-suggest-create-dataset: " +
|
222
|
+
"<#{packed_command_line}>: #{$!}")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def execute_script(script_path)
|
227
|
+
executor = create_sub_executor(@context)
|
228
|
+
executor.execute(resolve_path(script_path))
|
229
|
+
end
|
230
|
+
|
231
|
+
def extract_command_info(command)
|
232
|
+
@current_command = command
|
233
|
+
if @current_command.name == "dump"
|
234
|
+
@output_type = "groonga-command"
|
235
|
+
else
|
236
|
+
@output_type = @current_command[:output_type] || @context.output_type
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def execute_command(command)
|
241
|
+
extract_command_info(command)
|
242
|
+
log_input("#{command.original_source}\n")
|
243
|
+
response = send_command(command)
|
244
|
+
type = @output_type
|
245
|
+
log_output(response)
|
246
|
+
log_error(extract_important_messages(read_all_log))
|
247
|
+
|
248
|
+
@context.error if error_response?(response, type)
|
249
|
+
end
|
250
|
+
|
251
|
+
def read_all_log
|
252
|
+
read_all_readable_content(context.log, :first_timeout => 0)
|
253
|
+
end
|
254
|
+
|
255
|
+
def extract_important_messages(log)
|
256
|
+
important_messages = ""
|
257
|
+
log.each_line do |line|
|
258
|
+
timestamp, log_level, message = line.split(/\|\s*/, 3)
|
259
|
+
_ = timestamp # suppress warning
|
260
|
+
next unless important_log_level?(log_level)
|
261
|
+
next if backtrace_log_message?(message)
|
262
|
+
important_messages << "\#|#{log_level}| #{message}"
|
263
|
+
end
|
264
|
+
important_messages.chomp
|
265
|
+
end
|
266
|
+
|
267
|
+
def read_all_readable_content(output, options={})
|
268
|
+
content = ""
|
269
|
+
first_timeout = options[:first_timeout] || 1
|
270
|
+
timeout = first_timeout
|
271
|
+
while IO.select([output], [], [], timeout)
|
272
|
+
break if output.eof?
|
273
|
+
request_bytes = 1024
|
274
|
+
read_content = output.readpartial(request_bytes)
|
275
|
+
content << read_content
|
276
|
+
timeout = 0 if read_content.bytesize < request_bytes
|
277
|
+
end
|
278
|
+
content
|
279
|
+
end
|
280
|
+
|
281
|
+
def important_log_level?(log_level)
|
282
|
+
["E", "A", "C", "e", "w"].include?(log_level)
|
283
|
+
end
|
284
|
+
|
285
|
+
def backtrace_log_message?(message)
|
286
|
+
message.start_with?("/")
|
287
|
+
end
|
288
|
+
|
289
|
+
def error_response?(response, type)
|
290
|
+
status = nil
|
291
|
+
begin
|
292
|
+
status, = ResponseParser.parse(response, type)
|
293
|
+
rescue ParseError
|
294
|
+
return false
|
295
|
+
end
|
296
|
+
|
297
|
+
return_code, = status
|
298
|
+
return_code != ReturnCode::SUCCESS
|
299
|
+
end
|
300
|
+
|
301
|
+
def log(tag, content, options={})
|
302
|
+
return unless @context.logging?
|
303
|
+
log_force(tag, content, options)
|
304
|
+
end
|
305
|
+
|
306
|
+
def log_force(tag, content, options)
|
307
|
+
return if content.empty?
|
308
|
+
@context.result << [tag, content, options]
|
309
|
+
end
|
310
|
+
|
311
|
+
def log_input(content)
|
312
|
+
log(:input, content)
|
313
|
+
end
|
314
|
+
|
315
|
+
def log_output(content)
|
316
|
+
log(:output, content,
|
317
|
+
:command => @current_command,
|
318
|
+
:type => @output_type)
|
319
|
+
@current_command = nil
|
320
|
+
@output_type = nil
|
321
|
+
end
|
322
|
+
|
323
|
+
def log_error(content)
|
324
|
+
log_force(:error, content, {})
|
325
|
+
end
|
326
|
+
|
327
|
+
def default_long_timeout
|
328
|
+
180
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|