spectre-core 1.12.1 → 1.12.4
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 +4 -4
- data/exe/spectre +49 -15
- data/lib/spectre/assertion.rb +2 -2
- data/lib/spectre/curl.rb +5 -2
- data/lib/spectre/http.rb +9 -3
- data/lib/spectre/logger/console.rb +8 -2
- data/lib/spectre/logger/file.rb +9 -3
- data/lib/spectre/logger.rb +2 -2
- data/lib/spectre/reporter/console.rb +2 -2
- data/lib/spectre/reporter/junit.rb +6 -4
- data/lib/spectre.rb +16 -3
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d2cb2622682713c0fc37e7c0e679aa0d93f01ac9af389c8430496a272608038
|
4
|
+
data.tar.gz: 8b313cd7952c0b9e4baf9e8f0cec0af397d1f8cf8fc25760204714f687718df8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3e939b9e9851ece96d27e31d19fa7f4c5b26bd9141e28e2e5964b5f20ed1f9c9b0f4a77800b87c411d1c271d0b4acf1afa1f50359e832842e241a1ba536b811
|
7
|
+
data.tar.gz: d105a5a9924cba80b028790a538945087ef0e19a30763e619b712c1d7b31b54259a03504a5a52d1f1cc24be6a03f1e8185d499ed9e7343a17655c4336f22dd40
|
data/exe/spectre
CHANGED
@@ -9,6 +9,11 @@ require 'ectoplasm'
|
|
9
9
|
|
10
10
|
require_relative '../lib/spectre'
|
11
11
|
|
12
|
+
def load_yaml file_path
|
13
|
+
yaml = File.read(file_path)
|
14
|
+
YAML.safe_load(yaml, aliases: true)
|
15
|
+
end
|
16
|
+
|
12
17
|
|
13
18
|
DEFAULT_CONFIG = {
|
14
19
|
'config_file' => './spectre.yml',
|
@@ -72,9 +77,10 @@ DEFAULT_CONFIG = {
|
|
72
77
|
|
73
78
|
|
74
79
|
cmd_options = {}
|
80
|
+
property_overrides = {}
|
75
81
|
|
76
82
|
opt_parser = OptionParser.new do |opts|
|
77
|
-
|
83
|
+
opts.banner = %{Spectre #{Spectre::VERSION}
|
78
84
|
|
79
85
|
Usage: spectre [command] [options]
|
80
86
|
|
@@ -83,6 +89,7 @@ Commands:
|
|
83
89
|
run Run specs (default)
|
84
90
|
show Print current environment settings
|
85
91
|
dump Dumps the given environment in YAML format to console
|
92
|
+
cleanup Will remove all generated files (e.g. logs and reports)
|
86
93
|
init Initializes a new spectre project
|
87
94
|
|
88
95
|
Specific options:}
|
@@ -132,17 +139,24 @@ Specific options:}
|
|
132
139
|
end
|
133
140
|
|
134
141
|
opts.on('-p KEY=VAL', '--property KEY=VAL', "Override config option. Use `spectre show` to get list of available options") do |option|
|
135
|
-
key, val = option.split
|
136
|
-
val = val.split
|
137
|
-
val = ['true', '1'].include? val if [true, false].include?
|
142
|
+
key, val = option.split('=')
|
143
|
+
val = val.split(',') if DEFAULT_CONFIG[key].is_a? Array
|
144
|
+
val = ['true', '1'].include? val if [true, false].include?(DEFAULT_CONFIG[key])
|
138
145
|
val = val.to_i if DEFAULT_CONFIG[key].is_a? Integer
|
139
|
-
cmd_options[key] = val
|
140
146
|
|
141
|
-
|
142
|
-
|
143
|
-
|
147
|
+
opt_path = key.split('.')
|
148
|
+
|
149
|
+
curr_opt = property_overrides
|
150
|
+
|
151
|
+
opt_path.each_with_index do |part, i|
|
152
|
+
if i == opt_path.count-1
|
153
|
+
curr_opt[part] = val
|
154
|
+
break
|
155
|
+
end
|
156
|
+
|
157
|
+
curr_opt[part] = {} unless curr_opt.key?(part)
|
158
|
+
curr_opt = curr_opt[part]
|
144
159
|
end
|
145
|
-
curr_opt = val
|
146
160
|
end
|
147
161
|
|
148
162
|
opts.separator "\nCommon options:"
|
@@ -173,21 +187,20 @@ cfg.deep_merge! DEFAULT_CONFIG
|
|
173
187
|
global_config_file = File.join File.expand_path('~'), '.spectre'
|
174
188
|
|
175
189
|
if File.exists? global_config_file
|
176
|
-
global_options =
|
190
|
+
global_options = load_yaml(global_config_file)
|
177
191
|
cfg.deep_merge! global_options if global_options
|
178
192
|
end
|
179
193
|
|
180
194
|
config_file = cmd_options['config_file'] || cfg['config_file']
|
181
195
|
|
182
196
|
if File.exists? config_file
|
183
|
-
file_options =
|
197
|
+
file_options = load_yaml(config_file)
|
184
198
|
cfg.deep_merge! file_options
|
185
199
|
Dir.chdir File.dirname(config_file)
|
186
200
|
end
|
187
201
|
|
188
202
|
cfg.deep_merge! cmd_options
|
189
203
|
|
190
|
-
|
191
204
|
###########################################
|
192
205
|
# Load Environment
|
193
206
|
###########################################
|
@@ -197,7 +210,7 @@ envs = {}
|
|
197
210
|
read_env_files = {}
|
198
211
|
cfg['env_patterns'].each do |pattern|
|
199
212
|
Dir.glob(pattern).each do|f|
|
200
|
-
spec_env =
|
213
|
+
spec_env = load_yaml(f) || {}
|
201
214
|
|
202
215
|
name = spec_env['name'] || 'default'
|
203
216
|
|
@@ -215,7 +228,7 @@ end
|
|
215
228
|
# Merge partial environment configs with existing environments
|
216
229
|
cfg['env_partial_patterns'].each do |pattern|
|
217
230
|
Dir.glob(pattern).each do|f|
|
218
|
-
partial_env =
|
231
|
+
partial_env = load_yaml(f)
|
219
232
|
name = partial_env.delete('name') || 'default'
|
220
233
|
next unless envs.key? name
|
221
234
|
|
@@ -224,7 +237,10 @@ cfg['env_partial_patterns'].each do |pattern|
|
|
224
237
|
end
|
225
238
|
|
226
239
|
env = envs[cfg['environment']]
|
227
|
-
cfg.
|
240
|
+
cfg.deep_merge! env if env
|
241
|
+
|
242
|
+
# Merge property overrides after environment load to give it higher priority
|
243
|
+
cfg.deep_merge! property_overrides
|
228
244
|
|
229
245
|
|
230
246
|
String.colored! if cfg['colored']
|
@@ -392,6 +408,24 @@ if 'dump' == action
|
|
392
408
|
end
|
393
409
|
|
394
410
|
|
411
|
+
###########################################
|
412
|
+
# Cleanup
|
413
|
+
###########################################
|
414
|
+
|
415
|
+
|
416
|
+
if 'cleanup' == action
|
417
|
+
log_file_pattern = cfg['log_file'].gsub('<date>', '*')
|
418
|
+
|
419
|
+
Dir.glob(log_file_pattern).each do |log_file|
|
420
|
+
File.delete(log_file)
|
421
|
+
end
|
422
|
+
|
423
|
+
Dir.glob(File.join cfg['out_path'], '/*').each do |out_file|
|
424
|
+
File.delete(out_file)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
|
395
429
|
###########################################
|
396
430
|
# Init
|
397
431
|
###########################################
|
data/lib/spectre/assertion.rb
CHANGED
@@ -52,7 +52,7 @@ module Spectre
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def should_not_be_empty
|
55
|
-
raise AssertionFailure.new('The
|
55
|
+
raise AssertionFailure.new('The value does not exist', 'nil')
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -247,7 +247,7 @@ module Spectre
|
|
247
247
|
raise AssertionFailure.new(e.message, e.expected, e.actual, desc), cause: nil
|
248
248
|
rescue Exception => e
|
249
249
|
Logger.log_status(desc, Logger::Status::ERROR)
|
250
|
-
raise AssertionFailure.new("An unexpected error
|
250
|
+
raise AssertionFailure.new("An unexpected error occurred during expectation: #{e.message}", nil, nil, desc), cause: e
|
251
251
|
end
|
252
252
|
end
|
253
253
|
|
data/lib/spectre/curl.rb
CHANGED
@@ -301,7 +301,10 @@ module Spectre::Curl
|
|
301
301
|
|
302
302
|
req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
|
303
303
|
req_log += header_to_s(req['headers'])
|
304
|
-
|
304
|
+
|
305
|
+
if req[:body] != nil and not req[:body].empty?
|
306
|
+
req_log += try_format_json(req['body'], pretty: true)
|
307
|
+
end
|
305
308
|
|
306
309
|
@@logger.info(req_log)
|
307
310
|
|
@@ -329,7 +332,7 @@ module Spectre::Curl
|
|
329
332
|
|
330
333
|
exit_code = wait_thr.value.exitstatus
|
331
334
|
|
332
|
-
raise Exception.new "An error
|
335
|
+
raise Exception.new "An error occurred while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
|
333
336
|
|
334
337
|
# Parse protocol, version, status code and status message from response
|
335
338
|
match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
|
data/lib/spectre/http.rb
CHANGED
@@ -21,7 +21,8 @@ module Spectre
|
|
21
21
|
'query' => nil,
|
22
22
|
'content_type' => '',
|
23
23
|
'timeout' => 180,
|
24
|
-
|
24
|
+
'retries' => 0,
|
25
|
+
}
|
25
26
|
|
26
27
|
@@modules = []
|
27
28
|
|
@@ -57,6 +58,10 @@ module Spectre
|
|
57
58
|
@__req['timeout'] = seconds
|
58
59
|
end
|
59
60
|
|
61
|
+
def retries count
|
62
|
+
@__req['retries'] = count
|
63
|
+
end
|
64
|
+
|
60
65
|
def header name, value
|
61
66
|
@__req['headers'] ||= []
|
62
67
|
@__req['headers'].append [name, value.to_s.strip]
|
@@ -173,7 +178,7 @@ module Spectre
|
|
173
178
|
end
|
174
179
|
|
175
180
|
def http name, secure: false, &block
|
176
|
-
req =
|
181
|
+
req = DEFAULT_HTTP_CONFIG.clone
|
177
182
|
|
178
183
|
if @@http_cfg.key? name
|
179
184
|
req.deep_merge! @@http_cfg[name].deep_clone
|
@@ -270,11 +275,12 @@ module Spectre
|
|
270
275
|
|
271
276
|
net_http = Net::HTTP.new(uri.host, uri.port)
|
272
277
|
net_http.read_timeout = req['timeout']
|
278
|
+
net_http.max_retries = req['retries']
|
273
279
|
|
274
280
|
if uri.scheme == 'https'
|
275
281
|
net_http.use_ssl = true
|
276
282
|
|
277
|
-
if req
|
283
|
+
if req['cert']
|
278
284
|
raise HttpError.new("Certificate '#{req['cert']}' does not exist") unless File.exists? req['cert']
|
279
285
|
|
280
286
|
net_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
@@ -113,8 +113,14 @@ module Spectre
|
|
113
113
|
print_line('', txt)
|
114
114
|
end
|
115
115
|
|
116
|
-
def log_skipped _spec
|
117
|
-
|
116
|
+
def log_skipped _spec, message=nil
|
117
|
+
txt = Status::SKIPPED
|
118
|
+
|
119
|
+
unless message.nil?
|
120
|
+
txt += ' - ' + message
|
121
|
+
end
|
122
|
+
|
123
|
+
print_line('', txt.grey)
|
118
124
|
end
|
119
125
|
|
120
126
|
private
|
data/lib/spectre/logger/file.rb
CHANGED
@@ -79,11 +79,17 @@ module Spectre
|
|
79
79
|
|
80
80
|
def log_error spec, exception
|
81
81
|
file, line = exception.backtrace[0].match(/(.*\.rb):(\d+)/).captures
|
82
|
-
@file_log.error "An unexpected error
|
82
|
+
@file_log.error "An unexpected error occurred at '#{file}:#{line}' while running spec '#{spec.name}': [#{exception.class}] #{exception.message}\n#{exception.backtrace.join "\n"}"
|
83
83
|
end
|
84
84
|
|
85
|
-
def log_skipped spec
|
86
|
-
|
85
|
+
def log_skipped spec, message=nil
|
86
|
+
txt = "spec '#{spec.desc}' skipped"
|
87
|
+
|
88
|
+
unless message.nil?
|
89
|
+
txt += ': ' + message
|
90
|
+
end
|
91
|
+
|
92
|
+
@file_log.warn txt
|
87
93
|
end
|
88
94
|
|
89
95
|
def log_status desc, status, annotation=nil
|
data/lib/spectre/logger.rb
CHANGED
@@ -109,8 +109,8 @@ module Spectre
|
|
109
109
|
delegate(:log_error, spec, exception)
|
110
110
|
end
|
111
111
|
|
112
|
-
def log_skipped spec
|
113
|
-
delegate(:log_skipped, spec)
|
112
|
+
def log_skipped spec, message=nil
|
113
|
+
delegate(:log_skipped, spec, message)
|
114
114
|
end
|
115
115
|
|
116
116
|
def log_status desc, status, annotation=nil
|
@@ -21,7 +21,6 @@ module Spectre::Reporter
|
|
21
21
|
if run_info.failure
|
22
22
|
report_str += " Expected #{run_info.failure.expectation}"
|
23
23
|
report_str += " with #{run_info.data}" if run_info.data
|
24
|
-
report_str += " during #{spec.context.__desc}" if spec.context.__desc
|
25
24
|
|
26
25
|
report_str += " but it failed"
|
27
26
|
|
@@ -40,7 +39,7 @@ module Spectre::Reporter
|
|
40
39
|
failures += 1
|
41
40
|
|
42
41
|
else
|
43
|
-
report_str += " but an unexpected error
|
42
|
+
report_str += " but an unexpected error occurred during run\n"
|
44
43
|
report_str += format_exception(run_info.error)
|
45
44
|
errors += 1
|
46
45
|
end
|
@@ -68,6 +67,7 @@ module Spectre::Reporter
|
|
68
67
|
|
69
68
|
def format_title run_info
|
70
69
|
title = run_info.spec.subject.desc
|
70
|
+
title += " #{run_info.spec.context.__desc}" if run_info.spec.context.__desc
|
71
71
|
title += ' ' + run_info.spec.desc
|
72
72
|
title += " (#{'%.3f' % run_info.duration}s)"
|
73
73
|
title += " [#{run_info.spec.name}]"
|
@@ -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
|
|
@@ -22,11 +24,11 @@ module Spectre::Reporter
|
|
22
24
|
errors = run_infos.select { |x| x.error != nil }
|
23
25
|
skipped = run_infos.select { |x| x.skipped? }
|
24
26
|
|
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 + '">'
|
27
|
+
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.to_s + '" failures="' + failures.count.to_s + '" errors="' + errors.count.to_s + '" skipped="' + skipped.count.to_s + '">'
|
26
28
|
suite_id += 1
|
27
29
|
|
28
30
|
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) + '">'
|
31
|
+
xml_str += '<testcase classname="' + CGI::escapeHTML(run_info.spec.file.to_s) + '" name="' + CGI::escapeHTML(run_info.spec.desc) + '" timestamp="' + run_info.started.to_s + '" time="' + ('%.3f' % run_info.duration) + '">'
|
30
32
|
|
31
33
|
if run_info.failure and !run_info.failure.cause
|
32
34
|
failure_message = "Expected #{run_info.failure.expectation}"
|
@@ -38,7 +40,7 @@ module Spectre::Reporter
|
|
38
40
|
failure_message += " but it failed"
|
39
41
|
end
|
40
42
|
|
41
|
-
xml_str += '<failure message="' + failure_message.gsub('"', '`') + '"></failure>'
|
43
|
+
xml_str += '<failure message="' + CGI::escapeHTML(failure_message.gsub('"', '`')) + '"></failure>'
|
42
44
|
end
|
43
45
|
|
44
46
|
|
@@ -49,7 +51,7 @@ module Spectre::Reporter
|
|
49
51
|
failure_message = error.message
|
50
52
|
text = error.backtrace.join "\n"
|
51
53
|
|
52
|
-
xml_str += '<error message="' + failure_message.gsub('"', '`') + '" type="' + type + '">'
|
54
|
+
xml_str += '<error message="' + CGI::escapeHTML(failure_message.gsub('"', '`')) + '" type="' + type + '">'
|
53
55
|
xml_str += '<![CDATA[' + text + ']]>'
|
54
56
|
xml_str += '</error>'
|
55
57
|
end
|
data/lib/spectre.rb
CHANGED
@@ -2,7 +2,7 @@ module Spectre
|
|
2
2
|
module Version
|
3
3
|
MAJOR = 1
|
4
4
|
MINOR = 12
|
5
|
-
TINY =
|
5
|
+
TINY = 4
|
6
6
|
end
|
7
7
|
|
8
8
|
VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].compact * '.'
|
@@ -48,6 +48,12 @@ module Spectre
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
class SpectreSkip < Interrupt
|
52
|
+
def initialize message
|
53
|
+
super message
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
51
57
|
|
52
58
|
###########################################
|
53
59
|
# Internal Classes
|
@@ -252,9 +258,12 @@ module Spectre
|
|
252
258
|
spec.block.call(data)
|
253
259
|
rescue ExpectationFailure => e
|
254
260
|
run_info.failure = e
|
261
|
+
rescue SpectreSkip => e
|
262
|
+
run_info.skipped = true
|
263
|
+
Logger.log_skipped spec, e.message
|
255
264
|
rescue Interrupt
|
256
265
|
run_info.skipped = true
|
257
|
-
Logger.log_skipped spec
|
266
|
+
Logger.log_skipped spec, 'canceled by user'
|
258
267
|
rescue Exception => e
|
259
268
|
run_info.error = e
|
260
269
|
Logger.log_error spec, e
|
@@ -438,9 +447,13 @@ module Spectre
|
|
438
447
|
def property key, val
|
439
448
|
Spectre::Runner.current.properties[key] = val
|
440
449
|
end
|
450
|
+
|
451
|
+
def skip message=nil
|
452
|
+
raise SpectreSkip.new(message)
|
453
|
+
end
|
441
454
|
end
|
442
455
|
|
443
|
-
delegate :describe, :property, to: Spectre
|
456
|
+
delegate :describe, :property, :skip, to: Spectre
|
444
457
|
end
|
445
458
|
|
446
459
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spectre-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.12.
|
4
|
+
version: 1.12.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Neubauer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ectoplasm
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.
|
19
|
+
version: 1.2.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.
|
26
|
+
version: 1.2.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: jsonpath
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
80
|
requirements:
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
83
|
+
version: 3.0.0
|
84
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '0'
|
89
89
|
requirements: []
|
90
|
-
rubygems_version: 3.
|
90
|
+
rubygems_version: 3.3.7
|
91
91
|
signing_key:
|
92
92
|
specification_version: 4
|
93
93
|
summary: Describe and run automated tests
|