spectre-core 1.12.2 → 1.13.0

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