spectre-core 1.12.2 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  # https://llg.cubic.org/docs/junit/
2
4
  # Azure mappings: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-test-results?view=azure-devops&tabs=junit%2Cyaml
3
5
 
@@ -7,38 +9,43 @@ module Spectre::Reporter
7
9
  @config = config
8
10
  end
9
11
 
12
+ def get_name run_info
13
+ run_name = "[#{run_info.spec.name}] #{run_info.spec.subject.desc}"
14
+ run_name += " - #{run_info.spec.context.__desc} -" unless run_info.spec.context.__desc.nil?
15
+ run_name += " #{run_info.spec.desc}"
16
+ run_name
17
+ end
18
+
10
19
  def report run_infos
11
20
  now = Time.now.getutc
12
21
  timestamp = now.strftime('%s')
13
- datetime = now.strftime('%FT%T%:z')
22
+ datetime = now.strftime('%FT%T.%L')
14
23
 
15
24
  xml_str = '<?xml version="1.0" encoding="UTF-8" ?>'
16
25
  xml_str += '<testsuites>'
17
26
 
18
27
  suite_id = 0
19
28
 
20
- run_infos.group_by { |x| x.spec.subject }.each do |subject, run_infos|
21
- failures = run_infos.select { |x| x.failure != nil }
22
- errors = run_infos.select { |x| x.error != nil }
23
- skipped = run_infos.select { |x| x.skipped? }
29
+ run_infos.group_by { |x| x.spec.subject }.each do |subject, info_group|
30
+ failures = info_group.select { |x| x.failure != nil }
31
+ errors = info_group.select { |x| x.error != nil }
32
+ skipped = info_group.select { |x| x.skipped? }
24
33
 
25
- xml_str += '<testsuite package="' + subject.desc + '" id="' + suite_id.to_s + '" name="' + subject.desc + '" timestamp="' + datetime + '" tests="' + run_infos.count.to_s + '" failures="' + failures.count.to_s + '" errors="' + errors.count.to_s + '" skipped="' + skipped.count.to_s + '">'
34
+ xml_str += %{<testsuite package="#{CGI::escapeHTML(subject.desc)}" id="#{CGI::escapeHTML(suite_id.to_s)}" name="#{CGI::escapeHTML(subject.desc)}" timestamp="#{datetime}" tests="#{run_infos.count}" failures="#{failures.count}" errors="#{errors.count}" skipped="#{skipped.count}">}
26
35
  suite_id += 1
27
36
 
28
- run_infos.each do |run_info|
29
- xml_str += '<testcase classname="' + run_info.spec.file.to_s + '" name="' + run_info.spec.desc + '" timestamp="' + run_info.started.to_s + '" time="' + ('%.3f' % run_info.duration) + '">'
37
+ info_group.each do |run_info|
38
+ started = run_info.started.strftime('%FT%T.%L')
39
+
40
+ xml_str += %{<testcase classname="#{CGI::escapeHTML(run_info.spec.file.to_s)}" name="#{get_name(run_info)}" timestamp="#{started}" time="#{('%.3f' % run_info.duration)}">}
30
41
 
31
42
  if run_info.failure and !run_info.failure.cause
32
43
  failure_message = "Expected #{run_info.failure.expectation}"
33
44
  failure_message += " with #{run_info.data}" if run_info.data
45
+ failure_message += " but it failed"
46
+ failure_message += " with message: #{run_info.failure.message}" if run_info.failure.message
34
47
 
35
- if run_info.failure.message
36
- failure_message += " but it failed with #{run_info.failure.message}"
37
- else
38
- failure_message += " but it failed"
39
- end
40
-
41
- xml_str += '<failure message="' + failure_message.gsub('"', '`') + '"></failure>'
48
+ xml_str += %{<failure message="#{CGI::escapeHTML(failure_message.gsub('"', '`'))}"></failure>}
42
49
  end
43
50
 
44
51
 
@@ -49,8 +56,8 @@ module Spectre::Reporter
49
56
  failure_message = error.message
50
57
  text = error.backtrace.join "\n"
51
58
 
52
- xml_str += '<error message="' + failure_message.gsub('"', '`') + '" type="' + type + '">'
53
- xml_str += '<![CDATA[' + text + ']]>'
59
+ xml_str += %{<error message="#{CGI::escapeHTML(failure_message.gsub('"', '`'))}" type="#{type}">}
60
+ xml_str += "<![CDATA[#{text}]]>"
54
61
  xml_str += '</error>'
55
62
  end
56
63
 
@@ -67,7 +74,7 @@ module Spectre::Reporter
67
74
 
68
75
  if run_info.data
69
76
  data_str = run_info.data
70
- data_str = run_info.data.inspect unless run_info.data.is_a? String or run_info.data.is_a? Integer
77
+ data_str = run_info.data.to_json unless run_info.data.is_a? String or run_info.data.is_a? Integer
71
78
  xml_str += "data: #{data_str}\n"
72
79
  end
73
80
 
@@ -92,9 +99,7 @@ module Spectre::Reporter
92
99
 
93
100
  file_path = File.join(@config['out_path'], "spectre-junit_#{timestamp}.xml")
94
101
 
95
- File.open(file_path, 'w') do |file|
96
- file.write(xml_str)
97
- end
102
+ File.write(file_path, xml_str)
98
103
  end
99
104
  end
100
105
  end
@@ -0,0 +1,167 @@
1
+ require 'cgi'
2
+ require 'socket'
3
+ require 'securerandom'
4
+
5
+ # Azure mappings: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-test-results?view=azure-devops&tabs=trx%2Cyaml
6
+
7
+ module Spectre::Reporter
8
+ class VSTest
9
+ def initialize config
10
+ @config = config
11
+ @date_format = '%FT%T.%L'
12
+ end
13
+
14
+ def report run_infos
15
+ now = Time.now.getutc
16
+
17
+ xml_str = '<?xml version="1.0" encoding="UTF-8" ?>'
18
+ xml_str += %{<TestRun xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">}
19
+
20
+ started = run_infos[0].started
21
+ finished = run_infos[-1].finished
22
+
23
+ computer_name = Socket.gethostname
24
+
25
+ xml_str += %{<Times start="#{started.strftime(@date_format)}" finish="#{finished.strftime(@date_format)}" />}
26
+
27
+
28
+ # Write summary with file attachments
29
+ xml_str += '<ResultSummary>'
30
+ xml_str += '<ResultFiles>'
31
+ xml_str += %{<ResultFile path="#{File.absolute_path(@config['log_file'])}"></ResultFile>} if File.exists? @config['log_file']
32
+
33
+ report_files = Dir[File.join(@config['out_path'], '*')]
34
+
35
+ if report_files.any?
36
+ report_files.each do |report_file|
37
+ xml_str += %{<ResultFile path="#{File.absolute_path(report_file)}"></ResultFile>}
38
+ end
39
+ end
40
+
41
+ xml_str += '</ResultFiles>'
42
+ xml_str += '</ResultSummary>'
43
+
44
+
45
+ # Write test definitions
46
+ test_definitions = run_infos
47
+ .sort_by { |x| x.spec.name }
48
+ .map { |x| [SecureRandom.uuid(), SecureRandom.uuid(), x] }
49
+
50
+ xml_str += '<TestDefinitions>'
51
+ test_definitions.each do |test_id, execution_id, run_info|
52
+ xml_str += %{<UnitTest name="#{CGI::escapeHTML get_name(run_info)}" storage="#{CGI::escapeHTML(run_info.spec.file.to_s)}" id="#{test_id}">}
53
+ xml_str += %{<Execution id="#{execution_id}" />}
54
+ xml_str += '</UnitTest>'
55
+ end
56
+ xml_str += '</TestDefinitions>'
57
+
58
+
59
+ # Write test results
60
+ xml_str += '<Results>'
61
+ test_definitions.each do |test_id, execution_id, run_info|
62
+ duration_str = Time.at(run_info.duration).gmtime.strftime('%T.%L')
63
+
64
+ if run_info.failed?
65
+ outcome = 'Failed'
66
+ elsif run_info.error?
67
+ outcome = 'Error'
68
+ elsif run_info.skipped?
69
+ outcome = 'Skipped'
70
+ else
71
+ outcome = 'Passed'
72
+ end
73
+
74
+ xml_str += %{<UnitTestResult executionId="#{execution_id}" testId="#{test_id}" testName="#{CGI::escapeHTML get_name(run_info)}" computerName="#{computer_name}" duration="#{duration_str}" startTime="#{run_info.started.strftime(@date_format)}" endTime="#{run_info.finished.strftime(@date_format)}" outcome="#{outcome}">}
75
+
76
+ if run_info.log.any? or run_info.failed? or run_info.error?
77
+ xml_str += '<Output>'
78
+
79
+ # Write log entries
80
+ xml_str += '<StdOut>'
81
+ log_str = ''
82
+
83
+ if run_info.properties.count > 0
84
+ run_info.properties.each do |key, val|
85
+ log_str += "#{key}: #{val}\n"
86
+ end
87
+ end
88
+
89
+ if run_info.data
90
+ data_str = run_info.data
91
+ data_str = run_info.data.to_json unless run_info.data.is_a? String or run_info.data.is_a? Integer
92
+ log_str += "data: #{data_str}\n"
93
+ end
94
+
95
+ run_info.log.each do |timestamp, message, level, name|
96
+ log_str += %{#{timestamp.strftime(@date_format)} #{level.to_s.upcase} -- #{name}: #{CGI::escapeHTML(message.to_s)}\n}
97
+ end
98
+
99
+ xml_str += log_str
100
+ xml_str += '</StdOut>'
101
+
102
+ # Write error information
103
+ if run_info.failed? or run_info.error?
104
+ xml_str += '<ErrorInfo>'
105
+
106
+ if run_info.failed? and not run_info.failure.cause
107
+ xml_str += '<Message>'
108
+
109
+ failure_message = "Expected #{run_info.failure.expectation}"
110
+ failure_message += " with #{run_info.data}" if run_info.data
111
+ failure_message += " but it failed"
112
+ failure_message += " with message: #{run_info.failure.message}" if run_info.failure.message
113
+
114
+ xml_str += CGI::escapeHTML(failure_message)
115
+
116
+ xml_str += '</Message>'
117
+ end
118
+
119
+ if run_info.error or (run_info.failed? and run_info.failure.cause)
120
+ error = run_info.error || run_info.failure.cause
121
+
122
+ failure_message = error.message
123
+
124
+ xml_str += '<Message>'
125
+ xml_str += CGI::escapeHTML(failure_message)
126
+ xml_str += '</Message>'
127
+
128
+ stack_trace = error.backtrace.join "\n"
129
+
130
+ xml_str += '<StackTrace>'
131
+ xml_str += CGI::escapeHTML(stack_trace)
132
+ xml_str += '</StackTrace>'
133
+ end
134
+
135
+ xml_str += '</ErrorInfo>'
136
+ end
137
+
138
+ xml_str += '</Output>'
139
+ end
140
+
141
+
142
+ xml_str += '</UnitTestResult>'
143
+ end
144
+ xml_str += '</Results>'
145
+
146
+
147
+ # End report
148
+ xml_str += '</TestRun>'
149
+
150
+
151
+ Dir.mkdir(@config['out_path']) unless Dir.exists? @config['out_path']
152
+
153
+ file_path = File.join(@config['out_path'], "spectre-vstest_#{now.strftime('%s')}.trx")
154
+
155
+ File.write(file_path, xml_str)
156
+ end
157
+
158
+ private
159
+
160
+ def get_name run_info
161
+ run_name = "[#{run_info.spec.name}] #{run_info.spec.subject.desc}"
162
+ run_name += " - #{run_info.spec.context.__desc} -" unless run_info.spec.context.__desc.nil?
163
+ run_name += " #{run_info.spec.desc}"
164
+ run_name
165
+ end
166
+ end
167
+ end
data/lib/spectre.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Spectre
2
2
  module Version
3
3
  MAJOR = 1
4
- MINOR = 12
5
- TINY = 2
4
+ MINOR = 13
5
+ TINY = 0
6
6
  end
7
7
 
8
8
  VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].compact * '.'
@@ -10,7 +10,7 @@ module Spectre
10
10
 
11
11
  class ::Hash
12
12
  def deep_merge!(second)
13
- merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge!(v2, &merger) : v2 }
13
+ merger = proc { |_key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge!(v2, &merger) : v2 }
14
14
  self.merge!(second, &merger)
15
15
  end
16
16
 
@@ -34,9 +34,6 @@ module Spectre
34
34
 
35
35
 
36
36
  class SpectreError < Exception
37
- def initialize message
38
- super message
39
- end
40
37
  end
41
38
 
42
39
  class ExpectationFailure < Exception
@@ -48,6 +45,9 @@ module Spectre
48
45
  end
49
46
  end
50
47
 
48
+ class SpectreSkip < Interrupt
49
+ end
50
+
51
51
 
52
52
  ###########################################
53
53
  # Internal Classes
@@ -111,7 +111,7 @@ module Spectre
111
111
 
112
112
  class RunInfo
113
113
  attr_accessor :spec, :data, :started, :finished, :error, :failure, :skipped
114
- attr_reader :log, :properties
114
+ attr_reader :expectations, :log, :properties
115
115
 
116
116
  def initialize spec, data=nil
117
117
  @spec = spec
@@ -122,6 +122,7 @@ module Spectre
122
122
  @failure = nil
123
123
  @skipped = false
124
124
  @log = []
125
+ @expectations = []
125
126
  @properties = {}
126
127
  end
127
128
 
@@ -134,12 +135,24 @@ module Spectre
134
135
  end
135
136
 
136
137
  def failed?
138
+ @failure != nil
139
+ end
140
+
141
+ def error?
137
142
  @error != nil
138
143
  end
144
+
145
+ def status
146
+ return :error if error?
147
+ return :failed if failed?
148
+ return :skipped if skipped?
149
+
150
+ return :success
151
+ end
139
152
  end
140
153
 
141
154
  class Runner
142
- @@current
155
+ @@current = nil
143
156
 
144
157
  def self.current
145
158
  @@current
@@ -148,11 +161,11 @@ module Spectre
148
161
  def run specs
149
162
  runs = []
150
163
 
151
- specs.group_by { |x| x.subject }.each do |subject, spec_group|
152
- Logger.log_subject subject do
153
- spec_group.group_by { |x| x.context }.each do |context, specs|
154
- Logger.log_context(context) do
155
- runs.concat run_context(context, specs)
164
+ specs.group_by { |x| x.subject }.each do |subject, subject_specs|
165
+ Logging.log_subject subject do
166
+ subject_specs.group_by { |x| x.context }.each do |context, context_specs|
167
+ Logging.log_context(context) do
168
+ runs.concat run_context(context, context_specs)
156
169
  end
157
170
  end
158
171
  end
@@ -166,8 +179,8 @@ module Spectre
166
179
  def run_context context, specs
167
180
  runs = []
168
181
 
169
- if context.__setup_blocks.count > 0
170
- setup_run = run_blocks('setup', context, context.__setup_blocks)
182
+ context.__setup_blocks.each do |setup_spec|
183
+ setup_run = run_setup(setup_spec)
171
184
  runs << setup_run
172
185
  return runs if setup_run.error or setup_run.failure
173
186
  end
@@ -180,47 +193,42 @@ module Spectre
180
193
  spec.data
181
194
  .map { |x| x.is_a?(Hash) ? OpenStruct.new(x) : x }
182
195
  .each do |data|
183
- Logger.log_spec(spec, data) do
196
+ Logging.log_spec(spec, data) do
184
197
  runs << run_spec(spec, data)
185
198
  end
186
199
  end
187
200
  else
188
- Logger.log_spec(spec) do
201
+ Logging.log_spec(spec) do
189
202
  runs << run_spec(spec)
190
203
  end
191
204
  end
192
205
  end
193
206
  ensure
194
- if context.__teardown_blocks.count > 0
195
- runs << run_blocks('teardown', context, context.__teardown_blocks)
207
+ context.__teardown_blocks.each do |teardown_spec|
208
+ runs << run_setup(teardown_spec)
196
209
  end
197
210
  end
198
211
 
199
212
  runs
200
213
  end
201
214
 
202
- def run_blocks name, context, blocks
203
- ctx = SpecContext.new context.__subject, name
204
- spec = Spec.new name, context.__subject, name, [], nil, nil, ctx, nil
205
-
206
- run_info = RunInfo.new spec
215
+ def run_setup spec
216
+ run_info = RunInfo.new(spec)
207
217
 
208
218
  @@current = run_info
209
219
 
210
220
  run_info.started = Time.now
211
221
 
212
- Logger.log_context ctx do
222
+ Logging.log_context(spec.context) do
213
223
  begin
214
- blocks.each do |block|
215
- block.call
216
- end
224
+ spec.block.call()
217
225
 
218
226
  run_info.finished = Time.now
219
227
  rescue ExpectationFailure => e
220
228
  run_info.failure = e
221
229
  rescue Exception => e
222
230
  run_info.error = e
223
- Logger.log_error spec, e
231
+ Logging.log_error(spec, e)
224
232
  end
225
233
  end
226
234
 
@@ -240,9 +248,9 @@ module Spectre
240
248
 
241
249
  begin
242
250
  if spec.context.__before_blocks.count > 0
243
- before_ctx = SpecContext.new(spec.subject, 'before')
251
+ before_ctx = SpecContext.new(spec.subject, 'before', spec.context)
244
252
 
245
- Logger.log_context before_ctx do
253
+ Logging.log_context before_ctx do
246
254
  spec.context.__before_blocks.each do |block|
247
255
  block.call(data)
248
256
  end
@@ -252,17 +260,20 @@ module Spectre
252
260
  spec.block.call(data)
253
261
  rescue ExpectationFailure => e
254
262
  run_info.failure = e
263
+ rescue SpectreSkip => e
264
+ run_info.skipped = true
265
+ Logging.log_skipped(spec, e.message)
255
266
  rescue Interrupt
256
267
  run_info.skipped = true
257
- Logger.log_skipped spec
268
+ Logging.log_skipped(spec, 'canceled by user')
258
269
  rescue Exception => e
259
270
  run_info.error = e
260
- Logger.log_error spec, e
271
+ Logging.log_error(spec, e)
261
272
  ensure
262
273
  if spec.context.__after_blocks.count > 0
263
- after_ctx = SpecContext.new(spec.subject, 'after')
274
+ after_ctx = SpecContext.new(spec.subject, 'after', spec.context)
264
275
 
265
- Logger.log_context after_ctx do
276
+ Logging.log_context after_ctx do
266
277
  begin
267
278
  spec.context.__after_blocks.each do |block|
268
279
  block.call
@@ -273,7 +284,7 @@ module Spectre
273
284
  run_info.failure = e
274
285
  rescue Exception => e
275
286
  run_info.error = e
276
- Logger.log_error spec, e
287
+ Logging.log_error(spec, e)
277
288
  end
278
289
  end
279
290
  end
@@ -294,11 +305,12 @@ module Spectre
294
305
 
295
306
 
296
307
  class SpecContext < DslClass
297
- attr_reader :__subject, :__desc, :__before_blocks, :__after_blocks, :__setup_blocks, :__teardown_blocks
308
+ attr_reader :__subject, :__desc, :__parent, :__before_blocks, :__after_blocks, :__setup_blocks, :__teardown_blocks
298
309
 
299
- def initialize subject, desc=nil
310
+ def initialize subject, desc=nil, parent=nil
300
311
  @__subject = subject
301
312
  @__desc = desc
313
+ @__parent = parent
302
314
 
303
315
  @__before_blocks = []
304
316
  @__after_blocks = []
@@ -307,20 +319,7 @@ module Spectre
307
319
  end
308
320
 
309
321
  def it desc, tags: [], with: [], &block
310
- # Get the file, where the spec is defined.
311
- # Nasty, but it works
312
- # Maybe there is another way, but this works for now
313
- spec_file = nil
314
- begin
315
- raise
316
- rescue => e
317
- spec_file = e.backtrace
318
- .select { |file| !file.include? 'lib/spectre' }
319
- .first
320
- .match(/(.*\.rb):\d+/)
321
- .captures
322
- .first
323
- end
322
+ spec_file = get_file()
324
323
 
325
324
  @__subject.add_spec(desc, tags, with, block, self, spec_file)
326
325
  end
@@ -334,17 +333,44 @@ module Spectre
334
333
  end
335
334
 
336
335
  def setup &block
337
- @__setup_blocks << block
336
+ name = "#{@__subject.name}-setup"
337
+ spec_file = get_file()
338
+
339
+ setup_ctx = SpecContext.new(@__subject, 'setup', self)
340
+ @__setup_blocks << Spec.new(name, @__subject, 'setup', [], nil, block, setup_ctx, spec_file)
338
341
  end
339
342
 
340
343
  def teardown &block
341
- @__teardown_blocks << block
344
+ name = "#{@__subject.name}-teardown"
345
+ spec_file = get_file()
346
+
347
+ teardown_ctx = SpecContext.new(@__subject, 'teardown', self)
348
+ @__teardown_blocks << Spec.new(name, @__subject, 'teardown', [], nil, block, teardown_ctx, spec_file)
342
349
  end
343
350
 
344
351
  def context desc=nil, &block
345
- ctx = SpecContext.new(@__subject, desc)
352
+ ctx = SpecContext.new(@__subject, desc, self)
346
353
  ctx._evaluate &block
347
354
  end
355
+
356
+ private
357
+
358
+ def get_file
359
+ # Get the file, where the spec is defined.
360
+ # Nasty, but it works
361
+ # Maybe there is another way, but this works for now
362
+
363
+ begin
364
+ raise
365
+ rescue => e
366
+ return e.backtrace
367
+ .select { |file| !file.include? 'lib/spectre' }
368
+ .first
369
+ .match(/(.*\.rb):\d+/)
370
+ .captures
371
+ .first
372
+ end
373
+ end
348
374
  end
349
375
 
350
376
 
@@ -397,14 +423,14 @@ module Spectre
397
423
 
398
424
  def tag? tags, tag_exp
399
425
  tags = tags.map { |x| x.to_s }
400
- all_tags = tag_exp.split '+'
426
+ all_tags = tag_exp.split('+')
401
427
  included_tags = all_tags.select { |x| !x.start_with? '!' }
402
428
  excluded_tags = all_tags.select { |x| x.start_with? '!' }.map { |x| x[1..-1] }
403
429
  included_tags & tags == included_tags and excluded_tags & tags == []
404
430
  end
405
431
 
406
432
  def delegate *method_names, to: nil
407
- Spectre::Delegator.delegate *method_names, to
433
+ Spectre::Delegator.delegate(*method_names, to)
408
434
  end
409
435
 
410
436
  def register &block
@@ -438,9 +464,13 @@ module Spectre
438
464
  def property key, val
439
465
  Spectre::Runner.current.properties[key] = val
440
466
  end
467
+
468
+ def skip message=nil
469
+ raise SpectreSkip.new(message)
470
+ end
441
471
  end
442
472
 
443
- delegate :describe, :property, to: Spectre
473
+ delegate :describe, :property, :skip, to: Spectre
444
474
  end
445
475
 
446
476
 
Binary file
Binary file
Binary file