rspec-teamcity 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +30 -0
- data/Rakefile +2 -0
- data/lib/rspec/teamcity.rb +693 -0
- data/lib/rspec/teamcity/formatter_initializer.rb +50 -0
- data/lib/rspec/teamcity/rake_exceptions.rb +25 -0
- data/lib/rspec/teamcity/rakerunner_consts.rb +131 -0
- data/lib/rspec/teamcity/runner_common.rb +62 -0
- data/lib/rspec/teamcity/utils/logger_util.rb +90 -0
- data/lib/rspec/teamcity/utils/runner_utils.rb +182 -0
- data/lib/rspec/teamcity/utils/service_message_factory.rb +279 -0
- data/lib/rspec/teamcity/utils/std_capture_helper.rb +103 -0
- data/lib/rspec/teamcity/utils/string_ext.rb +28 -0
- data/lib/rspec/teamcity/utils/url_formatter.rb +35 -0
- data/lib/rspec/teamcity/version.rb +5 -0
- data/lib/rspec/teamcity_rspec3.rb +448 -0
- data/rspec-teamcity.gemspec +28 -0
- metadata +119 -0
@@ -0,0 +1,279 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2000-2012 JetBrains s.r.o.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
# @author: Roman Chernyatchik
|
18
|
+
######################
|
19
|
+
#noinspection RubyResolve
|
20
|
+
require_relative "../rakerunner_consts"
|
21
|
+
|
22
|
+
module Rake
|
23
|
+
module TeamCity
|
24
|
+
module MessageFactory
|
25
|
+
|
26
|
+
MSG_BLOCK_TYPES = {
|
27
|
+
# :build => "Build" # BLOCK_TYPE_BUILD
|
28
|
+
:progress => "$BUILD_PROGRESS$", # BLOCK_TYPE_PROGRESS
|
29
|
+
:test => "$TEST_BLOCK$", # BLOCK_TYPE_TEST
|
30
|
+
:test_suite => "$TEST_SUITE$", # BLOCK_TYPE_TEST_SUITE
|
31
|
+
:compilation => "$COMPILATION_BLOCK$", # BLOCK_TYPE_COMPILATION
|
32
|
+
:target => "$TARGET_BLOCK$", # BLOCK_TYPE_TARGET
|
33
|
+
:task => "rakeTask"
|
34
|
+
}
|
35
|
+
|
36
|
+
MSG_STATUS_TYPES = {
|
37
|
+
:warning => "WARNING",
|
38
|
+
:error => "ERROR"
|
39
|
+
}
|
40
|
+
|
41
|
+
CUSTOM_MSG_TYPES = {
|
42
|
+
:started => 'testStarted',
|
43
|
+
:failed => 'testFailed'
|
44
|
+
}
|
45
|
+
|
46
|
+
MOCK_ATTRIBUTES_VALUES = {
|
47
|
+
:details => {:value => '##STACK_TRACE##', :enabled => ::Rake::TeamCity.is_fake_stacktrace_enabled?, :remove_empty => true},
|
48
|
+
:errorDetails => {:value => '##STACK_TRACE##', :enabled => ::Rake::TeamCity.is_fake_stacktrace_enabled?, :remove_empty => true},
|
49
|
+
:locationHint => {:value => '##LOCATION_URL##', :enabled => ::Rake::TeamCity.is_fake_location_url_enabled?, :remove_empty => true},
|
50
|
+
:duration => {:value => '##DURATION##', :enabled => ::Rake::TeamCity.is_fake_time_enabled?},
|
51
|
+
:time => {:value => '##TIME##', :enabled => ::Rake::TeamCity.is_fake_time_enabled?},
|
52
|
+
:error_msg => {:value => '##MESSAGE##', :enabled => ::Rake::TeamCity.is_fake_error_msg_enabled?}
|
53
|
+
}
|
54
|
+
|
55
|
+
def self.create_suite_started(suite_name, location_url = nil)
|
56
|
+
create_message :message_name => "testSuiteStarted",
|
57
|
+
:name => suite_name,
|
58
|
+
:locationHint => location_url
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_suite_finished(suite_name)
|
62
|
+
create_message :message_name => "testSuiteFinished",
|
63
|
+
:name => suite_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.create_tests_count(int_count)
|
67
|
+
create_message :message_name => "testCount",
|
68
|
+
:count => int_count
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create_test_started(test_name, location_url = nil)
|
72
|
+
create_message :message_name => "testStarted",
|
73
|
+
:name => test_name,
|
74
|
+
:captureStandardOutput => 'true',
|
75
|
+
:locationHint => location_url
|
76
|
+
end
|
77
|
+
|
78
|
+
# Duration in millisec
|
79
|
+
def self.create_test_finished(test_name, duration_ms, diagnostic_info=nil)
|
80
|
+
create_message :message_name => "testFinished",
|
81
|
+
:name => test_name,
|
82
|
+
:duration => [duration_ms, 0].max,
|
83
|
+
:diagnosticInfo => diagnostic_info
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.create_test_output_message(test_name, is_std_out, out_text)
|
87
|
+
create_message :message_name => "testStd#{is_std_out ? "Out" : "Err"}",
|
88
|
+
:name => test_name,
|
89
|
+
:out => out_text
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.create_test_failed(test_name, message, stacktrace)
|
93
|
+
stacktrace = format_stacktrace_if_needed(message, stacktrace)
|
94
|
+
create_message :message_name => 'testFailed',
|
95
|
+
:name => test_name,
|
96
|
+
:message => message,
|
97
|
+
:details => stacktrace
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.create_test_error(test_name, message, stacktrace)
|
101
|
+
stacktrace = format_stacktrace_if_needed(message, stacktrace)
|
102
|
+
create_message :message_name => 'testFailed',
|
103
|
+
:name => test_name,
|
104
|
+
:message => message,
|
105
|
+
:details => stacktrace,
|
106
|
+
:error => 'true'
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.create_test_ignored(test_name, message, stacktrace = nil)
|
110
|
+
create_message :message_name => 'testIgnored',
|
111
|
+
:name => test_name,
|
112
|
+
:message => message,
|
113
|
+
:details => stacktrace
|
114
|
+
end
|
115
|
+
|
116
|
+
# This message should show progress on buildserver and can be
|
117
|
+
# ignored by IDE tests runners
|
118
|
+
def self.create_progress_message(message)
|
119
|
+
# This kind of message doesn't support timestamp attribute
|
120
|
+
create_message :message_name => 'progressMessage',
|
121
|
+
:message_text => message
|
122
|
+
end
|
123
|
+
|
124
|
+
# This message should show custom build status on buildserver and can be
|
125
|
+
# ignored by IDE tests runners
|
126
|
+
def self.create_build_error_report(message)
|
127
|
+
create_message :message_name => 'buildStatus',
|
128
|
+
:status => MSG_STATUS_TYPES[:error],
|
129
|
+
:text => message
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.create_msg_error(message, stacktrace = nil)
|
133
|
+
attrs = {
|
134
|
+
:message_name => 'message',
|
135
|
+
:text => message,
|
136
|
+
:status => MSG_STATUS_TYPES[:error],
|
137
|
+
:errorDetails => stacktrace
|
138
|
+
}
|
139
|
+
attrs[:text] &&= MOCK_ATTRIBUTES_VALUES[:error_msg][:value] if MOCK_ATTRIBUTES_VALUES[:error_msg][:enabled]
|
140
|
+
|
141
|
+
create_message attrs
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.create_msg_warning(message, stacktrace = nil)
|
145
|
+
create_message :message_name => 'message',
|
146
|
+
:text => message,
|
147
|
+
:status => MSG_STATUS_TYPES[:warning],
|
148
|
+
:errorDetails => stacktrace
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.create_open_target(message)
|
152
|
+
create_message :message_name => 'blockOpened',
|
153
|
+
:name => message,
|
154
|
+
:type => MSG_BLOCK_TYPES[:target]
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.create_close_target(message)
|
158
|
+
create_message :message_name => 'blockClosed',
|
159
|
+
:name => message,
|
160
|
+
:type => MSG_BLOCK_TYPES[:target]
|
161
|
+
end
|
162
|
+
|
163
|
+
#noinspection RubyClassMethodNamingConvention
|
164
|
+
def self.create_custom_progress_tests_category(category_name, int_count = 0)
|
165
|
+
create_message :message_name => 'customProgressStatus',
|
166
|
+
:testsCategory => category_name,
|
167
|
+
:count => int_count
|
168
|
+
end
|
169
|
+
|
170
|
+
#noinspection RubyClassMethodNamingConvention
|
171
|
+
def self.create_custom_progress_test_status(status)
|
172
|
+
create_message :message_name => 'customProgressStatus',
|
173
|
+
:type => CUSTOM_MSG_TYPES[status]
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.create_test_reported_attached
|
177
|
+
# Allows to distinguish 2 situations
|
178
|
+
# * nothing to test - no tests, suites
|
179
|
+
# * test reporter wasn't attached
|
180
|
+
# Can be reported several times
|
181
|
+
create_message :message_name => 'enteredTheMatrix'
|
182
|
+
end
|
183
|
+
|
184
|
+
###################################################################
|
185
|
+
###################################################################
|
186
|
+
###################################################################
|
187
|
+
|
188
|
+
def self.replace_escaped_symbols(text)
|
189
|
+
copy_of_text = String.new(text)
|
190
|
+
|
191
|
+
copy_of_text.gsub!(/\|/, "||")
|
192
|
+
|
193
|
+
copy_of_text.gsub!(/'/, "|'")
|
194
|
+
copy_of_text.gsub!(/\n/, "|n")
|
195
|
+
copy_of_text.gsub!(/\r/, "|r")
|
196
|
+
copy_of_text.gsub!(/\]/, "|]")
|
197
|
+
|
198
|
+
copy_of_text.gsub!(/\[/, "|[")
|
199
|
+
|
200
|
+
begin
|
201
|
+
copy_of_text.encode!('UTF-8') if copy_of_text.respond_to? :encode!
|
202
|
+
copy_of_text.gsub!(/\u0085/, "|x") # next line
|
203
|
+
copy_of_text.gsub!(/\u2028/, "|l") # line separator
|
204
|
+
copy_of_text.gsub!(/\u2029/, "|p") # paragraph separator
|
205
|
+
rescue
|
206
|
+
# it is not an utf-8 compatible string :(
|
207
|
+
end
|
208
|
+
|
209
|
+
copy_of_text
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
def self.format_stacktrace_if_needed message, stacktrace
|
214
|
+
if Rake::TeamCity.is_in_buildserver_mode()
|
215
|
+
# At current moment TC doesn't extract message from corresponding attribute.
|
216
|
+
# see [TW-6270] http://jetbrains.net/tracker/workspace?currentIssueId=TW-6270
|
217
|
+
message + "\n\nStack trace:\n" + stacktrace
|
218
|
+
else
|
219
|
+
stacktrace
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
protected
|
224
|
+
|
225
|
+
def self.create_message(msg_attrs = {})
|
226
|
+
# message type:
|
227
|
+
message_name = msg_attrs.delete(:message_name)
|
228
|
+
|
229
|
+
# optional body
|
230
|
+
message_text = msg_attrs[:message_text]
|
231
|
+
|
232
|
+
# if diagnostic info is null - don't pass it'
|
233
|
+
diagnostic = msg_attrs[:diagnosticInfo]
|
234
|
+
unless diagnostic
|
235
|
+
msg_attrs.delete(:diagnosticInfo)
|
236
|
+
end
|
237
|
+
|
238
|
+
if message_text.nil?
|
239
|
+
# mock some attrs
|
240
|
+
[:details, :errorDetails, :locationHint, :duration].each do |key|
|
241
|
+
if msg_attrs[key].nil?
|
242
|
+
# if key is nil - don't include in msg attrs
|
243
|
+
msg_attrs.delete(key) if MOCK_ATTRIBUTES_VALUES[key][:remove_empty]
|
244
|
+
else
|
245
|
+
# if not nil & debug mode - mock it
|
246
|
+
msg_attrs[key] = MOCK_ATTRIBUTES_VALUES[key][:value] if MOCK_ATTRIBUTES_VALUES[key][:enabled]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# add auto timestamp
|
251
|
+
msg_attrs[:timestamp] ||= convert_time_to_java_simple_date(Time.now)
|
252
|
+
|
253
|
+
# message args
|
254
|
+
message_args = msg_attrs.map { |k, v| "#{k.to_s} = '#{v.nil? ? "" : replace_escaped_symbols(v.to_s)}'" }.join(" ")
|
255
|
+
else
|
256
|
+
message_args = "'#{message_text}'"
|
257
|
+
end
|
258
|
+
|
259
|
+
"##teamcity[#{message_name}#{message_args.empty? ? '' : " #{message_args}"}]"
|
260
|
+
end
|
261
|
+
|
262
|
+
#noinspection RubyClassMethodNamingConvention
|
263
|
+
def self.convert_time_to_java_simple_date(time)
|
264
|
+
if MOCK_ATTRIBUTES_VALUES[:time][:enabled]
|
265
|
+
return MOCK_ATTRIBUTES_VALUES[:time][:value]
|
266
|
+
end
|
267
|
+
gmt_offset = time.gmt_offset
|
268
|
+
gmt_sign = gmt_offset < 0 ? "-" : "+"
|
269
|
+
gmt_hours = gmt_offset.abs / 3600
|
270
|
+
gmt_minutes = gmt_offset.abs % 3600
|
271
|
+
|
272
|
+
millisec = time.usec == 0 ? 0 : time.usec / 1000
|
273
|
+
|
274
|
+
#Time string in Java SimpleDateFormat
|
275
|
+
sprintf("#{time.strftime("%Y-%m-%dT%H:%M:%S.")}%03d#{gmt_sign}%02d%02d", millisec, gmt_hours, gmt_minutes)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright 2000-2012 JetBrains s.r.o.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Created by IntelliJ IDEA.
|
16
|
+
#
|
17
|
+
# @author: Roman.Chernyatchik
|
18
|
+
# @date: 10.01.2008
|
19
|
+
require_relative '../rakerunner_consts'
|
20
|
+
|
21
|
+
module Rake
|
22
|
+
module TeamCity
|
23
|
+
|
24
|
+
# Captures STDOUT and STDERR
|
25
|
+
module StdCaptureHelper
|
26
|
+
require 'tempfile'
|
27
|
+
|
28
|
+
def isCaptureDisabled()
|
29
|
+
ENV[TEAMCITY_RAKERUNNER_LOG_OUTPUT_CAPTURER_ENABLED_KEY] != "true"
|
30
|
+
end
|
31
|
+
|
32
|
+
def capture_output_start_external
|
33
|
+
old_out, old_err = copy_stdout_stderr
|
34
|
+
|
35
|
+
if isCaptureDisabled()
|
36
|
+
return old_out, old_err, nil, nil
|
37
|
+
end
|
38
|
+
|
39
|
+
new_out = Tempfile.new("tempfile_out")
|
40
|
+
new_err = Tempfile.new("tempfile_err")
|
41
|
+
|
42
|
+
reopen_stdout_stderr(new_out, new_err)
|
43
|
+
|
44
|
+
return old_out, old_err, new_out, new_err
|
45
|
+
end
|
46
|
+
|
47
|
+
def copy_stdout_stderr
|
48
|
+
if isCaptureDisabled()
|
49
|
+
return STDOUT, STDERR
|
50
|
+
else
|
51
|
+
return STDOUT.dup, STDERR.dup
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def reopen_stdout_stderr(sout, serr)
|
56
|
+
STDOUT.reopen(sout)
|
57
|
+
STDERR.reopen(serr)
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# returns STDOUT and STDERR content
|
62
|
+
def capture_output_end_external(old_out, old_err, new_out, new_err)
|
63
|
+
STDOUT.flush
|
64
|
+
STDERR.flush
|
65
|
+
|
66
|
+
if isCaptureDisabled()
|
67
|
+
return "", ""
|
68
|
+
end
|
69
|
+
|
70
|
+
reopen_stdout_stderr(old_out, old_err)
|
71
|
+
|
72
|
+
return get_redirected_stdout_stderr_from_files(new_out, new_err)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Closes files' streams and gets its output.
|
76
|
+
def get_redirected_stdout_stderr_from_files(new_out, new_err)
|
77
|
+
if isCaptureDisabled()
|
78
|
+
return "", ""
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
new_out.close
|
83
|
+
new_out.open
|
84
|
+
s_out = new_out.readlines.join
|
85
|
+
new_out.close
|
86
|
+
rescue Exception => ex
|
87
|
+
s_out = "Error: Teamcity agent is unable to capture STDOUT: #{ex}"
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
new_err.close
|
92
|
+
new_err.open
|
93
|
+
s_err = new_err.readlines.join
|
94
|
+
new_err.close
|
95
|
+
rescue Exception => ex
|
96
|
+
s_err = "Error: Teamcity agent is unable to capture STDERR: #{ex}"
|
97
|
+
end
|
98
|
+
|
99
|
+
return s_out, s_err
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright 2000-2012 JetBrains s.r.o.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Created by IntelliJ IDEA.
|
16
|
+
#
|
17
|
+
# @author: Roman.Chernyatchik
|
18
|
+
# @date: 02.06.2007
|
19
|
+
|
20
|
+
class String
|
21
|
+
def starts_with? str
|
22
|
+
self[0...str.length] == str
|
23
|
+
end
|
24
|
+
|
25
|
+
def substring index
|
26
|
+
self[index..size - 1]
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright 2000-2012 JetBrains s.r.o.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# @author: Roman.Chernyatchik
|
16
|
+
|
17
|
+
module Rake
|
18
|
+
module TeamCity
|
19
|
+
module Utils
|
20
|
+
module UrlFormatter
|
21
|
+
# @Nullable
|
22
|
+
def location_from_link(path, line_str)
|
23
|
+
return nil if (path.nil? || line_str.nil?)
|
24
|
+
"file://#{path}:#{line_str}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @Nullable
|
28
|
+
def location_from_ruby_qualified_name(qualified_name)
|
29
|
+
return nil if (qualified_name.nil?)
|
30
|
+
"ruby_qn://#{qualified_name}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|