spectre-core 1.12.0 → 1.12.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 +4 -4
- data/exe/spectre +516 -516
- data/lib/spectre/assertion.rb +10 -10
- data/lib/spectre/bag.rb +21 -21
- data/lib/spectre/curl.rb +397 -397
- data/lib/spectre/diagnostic.rb +39 -39
- data/lib/spectre/environment.rb +30 -30
- data/lib/spectre/helpers.rb +133 -133
- data/lib/spectre/http.rb +373 -364
- data/lib/spectre/logger/console.rb +143 -143
- data/lib/spectre/logger/file.rb +96 -96
- data/lib/spectre/logger.rb +146 -146
- data/lib/spectre/mixin.rb +58 -58
- data/lib/spectre/reporter/console.rb +101 -102
- data/lib/spectre/reporter/junit.rb +100 -100
- data/lib/spectre/resources.rb +49 -49
- data/lib/spectre.rb +447 -440
- metadata +12 -12
data/exe/spectre
CHANGED
@@ -1,516 +1,516 @@
|
|
1
|
-
#! /usr/bin/ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'yaml'
|
5
|
-
require 'ostruct'
|
6
|
-
require 'optparse'
|
7
|
-
require 'fileutils'
|
8
|
-
require 'ectoplasm'
|
9
|
-
|
10
|
-
require_relative '../lib/spectre'
|
11
|
-
|
12
|
-
|
13
|
-
DEFAULT_CONFIG = {
|
14
|
-
'config_file' => './spectre.yml',
|
15
|
-
'environment' => 'default',
|
16
|
-
'specs' => [],
|
17
|
-
'tags' => [],
|
18
|
-
'colored' => true,
|
19
|
-
'verbose' => false,
|
20
|
-
'reporters' => [
|
21
|
-
'Spectre::Reporter::Console',
|
22
|
-
],
|
23
|
-
'loggers' => [
|
24
|
-
'Spectre::Logger::Console',
|
25
|
-
'Spectre::Logger::File',
|
26
|
-
],
|
27
|
-
'log_file' => './logs/spectre_<date>.log',
|
28
|
-
'log_format' => {
|
29
|
-
'console' => {
|
30
|
-
'indent' => 2,
|
31
|
-
'width' => 80,
|
32
|
-
'end_context' => nil,
|
33
|
-
'separator' => '<indent><desc>',
|
34
|
-
},
|
35
|
-
'file' => {
|
36
|
-
'separator' => '-- <desc>',
|
37
|
-
'start_group' => "-- Start '<desc>'",
|
38
|
-
'end_group' => "-- End '<desc>'",
|
39
|
-
},
|
40
|
-
},
|
41
|
-
'debug' => false,
|
42
|
-
'out_path' => './reports',
|
43
|
-
'secure_keys' => ['password', 'secret', 'token', 'secure', 'authorization'],
|
44
|
-
'spec_patterns' => ['./specs/**/*.spec.rb'],
|
45
|
-
'mixin_patterns' => ['../common/mixins/**/*.mixin.rb', './mixins/**/*.mixin.rb'],
|
46
|
-
'env_patterns' => ['./environments/**/*.env.yml'],
|
47
|
-
'env_partial_patterns' => ['./environments/**/*.env.secret.yml'],
|
48
|
-
'resource_paths' => ['../common/resources', './resources'],
|
49
|
-
'modules' => [
|
50
|
-
'spectre/helpers',
|
51
|
-
'spectre/reporter/console',
|
52
|
-
'spectre/reporter/junit',
|
53
|
-
'spectre/logger/console',
|
54
|
-
'spectre/logger/file',
|
55
|
-
'spectre/assertion',
|
56
|
-
'spectre/diagnostic',
|
57
|
-
'spectre/environment',
|
58
|
-
'spectre/mixin',
|
59
|
-
'spectre/bag',
|
60
|
-
'spectre/http',
|
61
|
-
'spectre/http/basic_auth',
|
62
|
-
'spectre/http/keystone',
|
63
|
-
'spectre/resources',
|
64
|
-
],
|
65
|
-
'include' => [
|
66
|
-
|
67
|
-
],
|
68
|
-
'exclude' => [
|
69
|
-
|
70
|
-
],
|
71
|
-
}
|
72
|
-
|
73
|
-
|
74
|
-
cmd_options = {}
|
75
|
-
|
76
|
-
opt_parser = OptionParser.new do |opts|
|
77
|
-
opts.banner = %{Spectre #{Spectre::VERSION}
|
78
|
-
|
79
|
-
Usage: spectre [command] [options]
|
80
|
-
|
81
|
-
Commands:
|
82
|
-
list List specs
|
83
|
-
run Run specs (default)
|
84
|
-
show Print current environment settings
|
85
|
-
dump Dumps the given environment in YAML format to console
|
86
|
-
init Initializes a new spectre project
|
87
|
-
|
88
|
-
Specific options:}
|
89
|
-
|
90
|
-
opts.on('-s SPEC,SPEC', '--specs SPEC,SPEC', Array, 'The specs to run') do |specs|
|
91
|
-
cmd_options['specs'] = specs
|
92
|
-
end
|
93
|
-
|
94
|
-
opts.on('-t TAG,TAG', '--tags TAG,TAG', Array, 'Run only specs with given tags') do |tags|
|
95
|
-
cmd_options['tags'] = tags
|
96
|
-
end
|
97
|
-
|
98
|
-
opts.on('-e NAME', '--env NAME', 'Name of the environment to load') do |env_name|
|
99
|
-
cmd_options['environment'] = env_name
|
100
|
-
end
|
101
|
-
|
102
|
-
opts.on('-c FILE', '--config FILE', 'Config file to load') do |file_path|
|
103
|
-
cmd_options['config_file'] = file_path
|
104
|
-
end
|
105
|
-
|
106
|
-
opts.on('--spec-pattern PATTERN', Array, 'File pattern for spec files') do |spec_pattern|
|
107
|
-
cmd_options['spec_patterns'] = spec_pattern
|
108
|
-
end
|
109
|
-
|
110
|
-
opts.on('--env-pattern PATTERN', Array, 'File pattern for environment files') do |env_patterns|
|
111
|
-
cmd_options['env_patterns'] = env_patterns
|
112
|
-
end
|
113
|
-
|
114
|
-
opts.on('--no-color', 'Disable colored output') do
|
115
|
-
cmd_options['colored'] = false
|
116
|
-
end
|
117
|
-
|
118
|
-
opts.on('--ignore-failure', 'Always exit with code 0') do
|
119
|
-
cmd_options['ignore_failure'] = true
|
120
|
-
end
|
121
|
-
|
122
|
-
opts.on('-o PATH', '--out PATH', 'Output directory path') do |path|
|
123
|
-
cmd_options['out_path'] = File.absolute_path(path)
|
124
|
-
end
|
125
|
-
|
126
|
-
opts.on('-r NAME', '--reporters NAME', Array, "A list of reporters to use") do |reporters|
|
127
|
-
cmd_options['reporters'] = reporters
|
128
|
-
end
|
129
|
-
|
130
|
-
opts.on('-d', '--debug', "Run in debug mode") do
|
131
|
-
cmd_options['debug'] = true
|
132
|
-
end
|
133
|
-
|
134
|
-
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 ',' if DEFAULT_CONFIG[key].is_a? Array
|
137
|
-
val = ['true', '1'].include? val if [true, false].include? DEFAULT_CONFIG[key]
|
138
|
-
val = val.to_i if DEFAULT_CONFIG[key].is_a? Integer
|
139
|
-
cmd_options[key] = val
|
140
|
-
|
141
|
-
curr_opt = cmd_options
|
142
|
-
(key.split '.').each do |k|
|
143
|
-
curr_opt[k] = {} unless curr_opt.key? k
|
144
|
-
end
|
145
|
-
curr_opt = val
|
146
|
-
end
|
147
|
-
|
148
|
-
opts.separator "\nCommon options:"
|
149
|
-
|
150
|
-
opts.on_tail('--version', 'Print current installed version') do
|
151
|
-
puts Spectre::VERSION
|
152
|
-
exit
|
153
|
-
end
|
154
|
-
|
155
|
-
opts.on_tail('-h', '--help', 'Print this help') do
|
156
|
-
puts opts
|
157
|
-
exit
|
158
|
-
end
|
159
|
-
end.parse!
|
160
|
-
|
161
|
-
|
162
|
-
action = ARGV[0] || 'run'
|
163
|
-
|
164
|
-
|
165
|
-
###########################################
|
166
|
-
# Load Config
|
167
|
-
###########################################
|
168
|
-
|
169
|
-
|
170
|
-
cfg = {}
|
171
|
-
cfg.deep_merge! DEFAULT_CONFIG
|
172
|
-
|
173
|
-
global_config_file = File.join File.expand_path('~'), '.spectre'
|
174
|
-
|
175
|
-
if File.exists? global_config_file
|
176
|
-
global_options = YAML.load_file(global_config_file)
|
177
|
-
cfg.deep_merge! global_options if global_options
|
178
|
-
end
|
179
|
-
|
180
|
-
config_file = cmd_options['config_file'] || cfg['config_file']
|
181
|
-
|
182
|
-
if File.exists? config_file
|
183
|
-
file_options = YAML.load_file(config_file)
|
184
|
-
cfg.deep_merge! file_options
|
185
|
-
Dir.chdir File.dirname(config_file)
|
186
|
-
end
|
187
|
-
|
188
|
-
cfg.deep_merge! cmd_options
|
189
|
-
|
190
|
-
|
191
|
-
###########################################
|
192
|
-
# Load Environment
|
193
|
-
###########################################
|
194
|
-
|
195
|
-
|
196
|
-
envs = {}
|
197
|
-
read_env_files = {}
|
198
|
-
cfg['env_patterns'].each do |pattern|
|
199
|
-
Dir.glob(pattern).each do|f|
|
200
|
-
spec_env = YAML.load_file(f) || {}
|
201
|
-
|
202
|
-
name = spec_env['name'] || 'default'
|
203
|
-
|
204
|
-
if envs.key? name
|
205
|
-
existing_env_file = read_env_files[name]
|
206
|
-
puts "Duplicate environment definition detected with name #{name} in '#{f}'. Previously defined in '#{existing_env_file}'"
|
207
|
-
exit 1
|
208
|
-
end
|
209
|
-
|
210
|
-
read_env_files[name] = f
|
211
|
-
envs[name] = spec_env
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Merge partial environment configs with existing environments
|
216
|
-
cfg['env_partial_patterns'].each do |pattern|
|
217
|
-
Dir.glob(pattern).each do|f|
|
218
|
-
partial_env = YAML.load_file(f)
|
219
|
-
name = partial_env.delete('name') || 'default'
|
220
|
-
next unless envs.key? name
|
221
|
-
|
222
|
-
envs[name].deep_merge! partial_env
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
env = envs[cfg['environment']]
|
227
|
-
cfg.merge! env if env
|
228
|
-
|
229
|
-
|
230
|
-
String.colored! if cfg['colored']
|
231
|
-
|
232
|
-
# Load environment exlicitly before loading specs to make it available in spec definition
|
233
|
-
require_relative '../lib/spectre/environment' unless cfg['exclude'].include? 'spectre/environment'
|
234
|
-
Spectre.configure(cfg)
|
235
|
-
|
236
|
-
|
237
|
-
###########################################
|
238
|
-
# Load Specs
|
239
|
-
###########################################
|
240
|
-
|
241
|
-
|
242
|
-
cfg['spec_patterns'].each do |pattern|
|
243
|
-
Dir.glob(pattern).each do|f|
|
244
|
-
require_relative File.join(Dir.pwd, f)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
|
249
|
-
###########################################
|
250
|
-
# List specs
|
251
|
-
###########################################
|
252
|
-
|
253
|
-
|
254
|
-
if 'list' == action
|
255
|
-
colors = [:blue, :magenta, :yellow, :green]
|
256
|
-
specs = Spectre.specs(cfg['specs'], cfg['tags'])
|
257
|
-
|
258
|
-
exit 1 unless specs.any?
|
259
|
-
|
260
|
-
counter = 0
|
261
|
-
|
262
|
-
specs.group_by { |x| x.subject }.each do |subject, spec_group|
|
263
|
-
spec_group.each do |spec|
|
264
|
-
tags = spec.tags.map { |x| '#' + x.to_s }.join ' '
|
265
|
-
desc = subject.desc
|
266
|
-
desc += ' - ' + spec.context.__desc + ' -' if spec.context.__desc
|
267
|
-
desc += ' ' + spec.desc
|
268
|
-
puts "[#{spec.name}]".send(colors[counter % colors.length]) + " #{desc} #{tags.cyan}"
|
269
|
-
end
|
270
|
-
|
271
|
-
counter += 1
|
272
|
-
end
|
273
|
-
|
274
|
-
exit 0
|
275
|
-
end
|
276
|
-
|
277
|
-
|
278
|
-
###########################################
|
279
|
-
# Run
|
280
|
-
###########################################
|
281
|
-
|
282
|
-
|
283
|
-
if 'run' == action
|
284
|
-
# Initialize logger
|
285
|
-
now = Time.now
|
286
|
-
|
287
|
-
cfg['log_file'] = cfg['log_file'].frmt(
|
288
|
-
{
|
289
|
-
shortdate: now.strftime('%Y-%m-%d'),
|
290
|
-
date: now.strftime('%Y-%m-%d_%H%M%S'),
|
291
|
-
timestamp: now.strftime('%s'),
|
292
|
-
subject: 'spectre',
|
293
|
-
})
|
294
|
-
|
295
|
-
log_dir = File.dirname cfg['log_file']
|
296
|
-
FileUtils.makedirs log_dir unless Dir.exists? log_dir
|
297
|
-
|
298
|
-
# Load Modules
|
299
|
-
|
300
|
-
cfg['modules']
|
301
|
-
.concat(cfg['include'])
|
302
|
-
.select { |mod| !cfg['exclude'].include? mod }
|
303
|
-
.each do |mod|
|
304
|
-
begin
|
305
|
-
mod_file = mod + '.rb'
|
306
|
-
spectre_lib_mod = File.join(File.dirname(__dir__), 'lib', mod_file)
|
307
|
-
|
308
|
-
if File.exists? mod_file
|
309
|
-
require_relative mod_file
|
310
|
-
|
311
|
-
elsif File.exists? spectre_lib_mod
|
312
|
-
require_relative spectre_lib_mod
|
313
|
-
|
314
|
-
else
|
315
|
-
require mod
|
316
|
-
end
|
317
|
-
rescue LoadError => e
|
318
|
-
puts "Unable to load module #{mod}. Check if the module exists or remove it from your spectre config:\n#{e.message}"
|
319
|
-
exit 1
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
# Load mixins
|
324
|
-
|
325
|
-
cfg['mixin_patterns'].each do |pattern|
|
326
|
-
Dir.glob(pattern).each do|f|
|
327
|
-
require_relative File.join(Dir.pwd, f)
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
Spectre.configure(cfg)
|
332
|
-
|
333
|
-
Spectre::Logger.debug! if cfg['debug']
|
334
|
-
|
335
|
-
cfg['loggers'].each do |logger_name|
|
336
|
-
logger = Kernel.const_get(logger_name).new(cfg)
|
337
|
-
Spectre::Logger.add(logger)
|
338
|
-
end if cfg['loggers']
|
339
|
-
|
340
|
-
specs = Spectre.specs(cfg['specs'], cfg['tags'])
|
341
|
-
|
342
|
-
unless specs.any?
|
343
|
-
puts "No specs found in #{Dir.pwd}"
|
344
|
-
exit 1
|
345
|
-
end
|
346
|
-
|
347
|
-
run_infos = Spectre::Runner.new.run(specs)
|
348
|
-
|
349
|
-
cfg['reporters'].each do |reporter|
|
350
|
-
reporter = Kernel.const_get(reporter).new(cfg)
|
351
|
-
reporter.report(run_infos)
|
352
|
-
end
|
353
|
-
|
354
|
-
errors = run_infos.select { |x| nil != x.error or nil != x.failure }
|
355
|
-
|
356
|
-
exit 0 if cfg['ignore_failure'] or not errors.any?
|
357
|
-
|
358
|
-
exit 1
|
359
|
-
end
|
360
|
-
|
361
|
-
|
362
|
-
###########################################
|
363
|
-
# Envs
|
364
|
-
###########################################
|
365
|
-
|
366
|
-
|
367
|
-
if 'envs' == action
|
368
|
-
exit 1 unless envs.any?
|
369
|
-
puts envs.pretty
|
370
|
-
exit 0
|
371
|
-
end
|
372
|
-
|
373
|
-
|
374
|
-
###########################################
|
375
|
-
# Show
|
376
|
-
###########################################
|
377
|
-
|
378
|
-
|
379
|
-
if 'show' == action
|
380
|
-
puts cfg.pretty
|
381
|
-
exit 0
|
382
|
-
end
|
383
|
-
|
384
|
-
|
385
|
-
###########################################
|
386
|
-
# Dump
|
387
|
-
###########################################
|
388
|
-
|
389
|
-
|
390
|
-
if 'dump' == action
|
391
|
-
puts YAML.dump(cfg)
|
392
|
-
end
|
393
|
-
|
394
|
-
|
395
|
-
###########################################
|
396
|
-
# Init
|
397
|
-
###########################################
|
398
|
-
|
399
|
-
DEFAULT_SPECTRE_CFG = %{log_file: ./logs/spectre_<date>.log
|
400
|
-
env_patterns:
|
401
|
-
- './environments/**/*.env.yml'
|
402
|
-
env_partial_patterns:
|
403
|
-
- './environments/**/*.env.secret.yml'
|
404
|
-
spec_patterns:
|
405
|
-
- './specs/**/*.spec.rb'
|
406
|
-
mixin_patterns:
|
407
|
-
- '../common/**/*.mixin.rb'
|
408
|
-
- './mixins/**/*.mixin.rb'
|
409
|
-
resource_paths:
|
410
|
-
- '../common/resources'
|
411
|
-
- './resources'
|
412
|
-
}
|
413
|
-
|
414
|
-
|
415
|
-
DEFAULT_ENV_CFG = %{cert: &cert ./resources/<root_cert>.cer
|
416
|
-
http:
|
417
|
-
<http_client_name>:
|
418
|
-
base_url: http://localhost:5000/api/v1/
|
419
|
-
# basic_auth:
|
420
|
-
# username: <username>
|
421
|
-
# password: <password>
|
422
|
-
# keystone:
|
423
|
-
# url: https://<keystone_url>/main/v3/
|
424
|
-
# username: <username>
|
425
|
-
# password: <password>
|
426
|
-
# project: <project>
|
427
|
-
# domain: <domain>
|
428
|
-
# cert: *cert
|
429
|
-
# ssh:
|
430
|
-
# <ssh_client_name>:
|
431
|
-
# host: <hostname>
|
432
|
-
# username: <username>
|
433
|
-
# password: <password>
|
434
|
-
}
|
435
|
-
|
436
|
-
DEFAULT_ENV_SECRET_CFG = %{http:
|
437
|
-
<http_client_name>:
|
438
|
-
# basic_auth:
|
439
|
-
# username: <username>
|
440
|
-
# password: <password>
|
441
|
-
# keystone:
|
442
|
-
# username: <username>
|
443
|
-
# password: <password>
|
444
|
-
# ssh:
|
445
|
-
# <ssh_client_name>:
|
446
|
-
# username: <username>
|
447
|
-
# password: <password>
|
448
|
-
}
|
449
|
-
|
450
|
-
SAMPLE_SPEC = %[describe '<subject>' do
|
451
|
-
it 'does some http requests', tags: [:sample] do
|
452
|
-
log 'doing some http request'
|
453
|
-
|
454
|
-
http '<http_client_name>' do
|
455
|
-
auth 'basic'
|
456
|
-
# auth 'keystone'
|
457
|
-
method 'GET'
|
458
|
-
path 'path/to/resource'
|
459
|
-
param 'id', 4295118773
|
460
|
-
param 'foo', 'bar'
|
461
|
-
header 'X-Correlation-Id', '4c2367b1-bfee-4cc2-bdc5-ed17a6a9dd4b'
|
462
|
-
header 'Range', 'bytes=500-999'
|
463
|
-
json({
|
464
|
-
"message": "Hello Spectre!"
|
465
|
-
})
|
466
|
-
end
|
467
|
-
|
468
|
-
expect 'the response code to be 200' do
|
469
|
-
response.code.should_be 200
|
470
|
-
end
|
471
|
-
|
472
|
-
expect 'a message to exist' do
|
473
|
-
response.json.message.should_not_be nil
|
474
|
-
end
|
475
|
-
end
|
476
|
-
end
|
477
|
-
]
|
478
|
-
|
479
|
-
DEFAULT_GITIGNORE = %[*.code-workspace
|
480
|
-
logs/
|
481
|
-
reports/
|
482
|
-
**/environments/*.env.secret.yml
|
483
|
-
]
|
484
|
-
|
485
|
-
DEFAULT_GEMFILE = %[source 'https://rubygems.org'
|
486
|
-
|
487
|
-
gem 'spectre-core', '>= #{Spectre::VERSION}'
|
488
|
-
# gem 'spectre-mysql', '>= 1.0.0'
|
489
|
-
# gem 'spectre-ssh', '>= 1.0.0'
|
490
|
-
# gem 'spectre-ftp', '>= 1.0.0'
|
491
|
-
# gem 'spectre-curl', '>= 1.0.0'
|
492
|
-
# gem 'spectre-git', '>= 0.1.0'
|
493
|
-
]
|
494
|
-
|
495
|
-
if 'init' == action
|
496
|
-
DEFAULT_FILES = [
|
497
|
-
['./environments/default.env.yml', DEFAULT_ENV_CFG],
|
498
|
-
['./environments/default.env.secret.yml', DEFAULT_ENV_SECRET_CFG],
|
499
|
-
['./specs/sample.spec.rb', SAMPLE_SPEC],
|
500
|
-
['./spectre.yml', DEFAULT_SPECTRE_CFG],
|
501
|
-
['./.gitignore', DEFAULT_GITIGNORE],
|
502
|
-
['./Gemfile', DEFAULT_GEMFILE],
|
503
|
-
]
|
504
|
-
|
505
|
-
%w(environments logs specs).each do |dir_name|
|
506
|
-
Dir.mkdir(dir_name) unless File.directory? dir_name
|
507
|
-
end
|
508
|
-
|
509
|
-
DEFAULT_FILES.each do |file, content|
|
510
|
-
unless File.exists? file
|
511
|
-
File.write(file, content)
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
exit 0
|
516
|
-
end
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'ectoplasm'
|
9
|
+
|
10
|
+
require_relative '../lib/spectre'
|
11
|
+
|
12
|
+
|
13
|
+
DEFAULT_CONFIG = {
|
14
|
+
'config_file' => './spectre.yml',
|
15
|
+
'environment' => 'default',
|
16
|
+
'specs' => [],
|
17
|
+
'tags' => [],
|
18
|
+
'colored' => true,
|
19
|
+
'verbose' => false,
|
20
|
+
'reporters' => [
|
21
|
+
'Spectre::Reporter::Console',
|
22
|
+
],
|
23
|
+
'loggers' => [
|
24
|
+
'Spectre::Logger::Console',
|
25
|
+
'Spectre::Logger::File',
|
26
|
+
],
|
27
|
+
'log_file' => './logs/spectre_<date>.log',
|
28
|
+
'log_format' => {
|
29
|
+
'console' => {
|
30
|
+
'indent' => 2,
|
31
|
+
'width' => 80,
|
32
|
+
'end_context' => nil,
|
33
|
+
'separator' => '<indent><desc>',
|
34
|
+
},
|
35
|
+
'file' => {
|
36
|
+
'separator' => '-- <desc>',
|
37
|
+
'start_group' => "-- Start '<desc>'",
|
38
|
+
'end_group' => "-- End '<desc>'",
|
39
|
+
},
|
40
|
+
},
|
41
|
+
'debug' => false,
|
42
|
+
'out_path' => './reports',
|
43
|
+
'secure_keys' => ['password', 'secret', 'token', 'secure', 'authorization'],
|
44
|
+
'spec_patterns' => ['./specs/**/*.spec.rb'],
|
45
|
+
'mixin_patterns' => ['../common/mixins/**/*.mixin.rb', './mixins/**/*.mixin.rb'],
|
46
|
+
'env_patterns' => ['./environments/**/*.env.yml'],
|
47
|
+
'env_partial_patterns' => ['./environments/**/*.env.secret.yml'],
|
48
|
+
'resource_paths' => ['../common/resources', './resources'],
|
49
|
+
'modules' => [
|
50
|
+
'spectre/helpers',
|
51
|
+
'spectre/reporter/console',
|
52
|
+
'spectre/reporter/junit',
|
53
|
+
'spectre/logger/console',
|
54
|
+
'spectre/logger/file',
|
55
|
+
'spectre/assertion',
|
56
|
+
'spectre/diagnostic',
|
57
|
+
'spectre/environment',
|
58
|
+
'spectre/mixin',
|
59
|
+
'spectre/bag',
|
60
|
+
'spectre/http',
|
61
|
+
'spectre/http/basic_auth',
|
62
|
+
'spectre/http/keystone',
|
63
|
+
'spectre/resources',
|
64
|
+
],
|
65
|
+
'include' => [
|
66
|
+
|
67
|
+
],
|
68
|
+
'exclude' => [
|
69
|
+
|
70
|
+
],
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
cmd_options = {}
|
75
|
+
|
76
|
+
opt_parser = OptionParser.new do |opts|
|
77
|
+
opts.banner = %{Spectre #{Spectre::VERSION}
|
78
|
+
|
79
|
+
Usage: spectre [command] [options]
|
80
|
+
|
81
|
+
Commands:
|
82
|
+
list List specs
|
83
|
+
run Run specs (default)
|
84
|
+
show Print current environment settings
|
85
|
+
dump Dumps the given environment in YAML format to console
|
86
|
+
init Initializes a new spectre project
|
87
|
+
|
88
|
+
Specific options:}
|
89
|
+
|
90
|
+
opts.on('-s SPEC,SPEC', '--specs SPEC,SPEC', Array, 'The specs to run') do |specs|
|
91
|
+
cmd_options['specs'] = specs
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on('-t TAG,TAG', '--tags TAG,TAG', Array, 'Run only specs with given tags') do |tags|
|
95
|
+
cmd_options['tags'] = tags
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on('-e NAME', '--env NAME', 'Name of the environment to load') do |env_name|
|
99
|
+
cmd_options['environment'] = env_name
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on('-c FILE', '--config FILE', 'Config file to load') do |file_path|
|
103
|
+
cmd_options['config_file'] = file_path
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on('--spec-pattern PATTERN', Array, 'File pattern for spec files') do |spec_pattern|
|
107
|
+
cmd_options['spec_patterns'] = spec_pattern
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.on('--env-pattern PATTERN', Array, 'File pattern for environment files') do |env_patterns|
|
111
|
+
cmd_options['env_patterns'] = env_patterns
|
112
|
+
end
|
113
|
+
|
114
|
+
opts.on('--no-color', 'Disable colored output') do
|
115
|
+
cmd_options['colored'] = false
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on('--ignore-failure', 'Always exit with code 0') do
|
119
|
+
cmd_options['ignore_failure'] = true
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.on('-o PATH', '--out PATH', 'Output directory path') do |path|
|
123
|
+
cmd_options['out_path'] = File.absolute_path(path)
|
124
|
+
end
|
125
|
+
|
126
|
+
opts.on('-r NAME', '--reporters NAME', Array, "A list of reporters to use") do |reporters|
|
127
|
+
cmd_options['reporters'] = reporters
|
128
|
+
end
|
129
|
+
|
130
|
+
opts.on('-d', '--debug', "Run in debug mode") do
|
131
|
+
cmd_options['debug'] = true
|
132
|
+
end
|
133
|
+
|
134
|
+
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 ',' if DEFAULT_CONFIG[key].is_a? Array
|
137
|
+
val = ['true', '1'].include? val if [true, false].include? DEFAULT_CONFIG[key]
|
138
|
+
val = val.to_i if DEFAULT_CONFIG[key].is_a? Integer
|
139
|
+
cmd_options[key] = val
|
140
|
+
|
141
|
+
curr_opt = cmd_options
|
142
|
+
(key.split '.').each do |k|
|
143
|
+
curr_opt[k] = {} unless curr_opt.key? k
|
144
|
+
end
|
145
|
+
curr_opt = val
|
146
|
+
end
|
147
|
+
|
148
|
+
opts.separator "\nCommon options:"
|
149
|
+
|
150
|
+
opts.on_tail('--version', 'Print current installed version') do
|
151
|
+
puts Spectre::VERSION
|
152
|
+
exit
|
153
|
+
end
|
154
|
+
|
155
|
+
opts.on_tail('-h', '--help', 'Print this help') do
|
156
|
+
puts opts
|
157
|
+
exit
|
158
|
+
end
|
159
|
+
end.parse!
|
160
|
+
|
161
|
+
|
162
|
+
action = ARGV[0] || 'run'
|
163
|
+
|
164
|
+
|
165
|
+
###########################################
|
166
|
+
# Load Config
|
167
|
+
###########################################
|
168
|
+
|
169
|
+
|
170
|
+
cfg = {}
|
171
|
+
cfg.deep_merge! DEFAULT_CONFIG
|
172
|
+
|
173
|
+
global_config_file = File.join File.expand_path('~'), '.spectre'
|
174
|
+
|
175
|
+
if File.exists? global_config_file
|
176
|
+
global_options = YAML.load_file(global_config_file)
|
177
|
+
cfg.deep_merge! global_options if global_options
|
178
|
+
end
|
179
|
+
|
180
|
+
config_file = cmd_options['config_file'] || cfg['config_file']
|
181
|
+
|
182
|
+
if File.exists? config_file
|
183
|
+
file_options = YAML.load_file(config_file)
|
184
|
+
cfg.deep_merge! file_options
|
185
|
+
Dir.chdir File.dirname(config_file)
|
186
|
+
end
|
187
|
+
|
188
|
+
cfg.deep_merge! cmd_options
|
189
|
+
|
190
|
+
|
191
|
+
###########################################
|
192
|
+
# Load Environment
|
193
|
+
###########################################
|
194
|
+
|
195
|
+
|
196
|
+
envs = {}
|
197
|
+
read_env_files = {}
|
198
|
+
cfg['env_patterns'].each do |pattern|
|
199
|
+
Dir.glob(pattern).each do|f|
|
200
|
+
spec_env = YAML.load_file(f) || {}
|
201
|
+
|
202
|
+
name = spec_env['name'] || 'default'
|
203
|
+
|
204
|
+
if envs.key? name
|
205
|
+
existing_env_file = read_env_files[name]
|
206
|
+
puts "Duplicate environment definition detected with name #{name} in '#{f}'. Previously defined in '#{existing_env_file}'"
|
207
|
+
exit 1
|
208
|
+
end
|
209
|
+
|
210
|
+
read_env_files[name] = f
|
211
|
+
envs[name] = spec_env
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Merge partial environment configs with existing environments
|
216
|
+
cfg['env_partial_patterns'].each do |pattern|
|
217
|
+
Dir.glob(pattern).each do|f|
|
218
|
+
partial_env = YAML.load_file(f)
|
219
|
+
name = partial_env.delete('name') || 'default'
|
220
|
+
next unless envs.key? name
|
221
|
+
|
222
|
+
envs[name].deep_merge! partial_env
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
env = envs[cfg['environment']]
|
227
|
+
cfg.merge! env if env
|
228
|
+
|
229
|
+
|
230
|
+
String.colored! if cfg['colored']
|
231
|
+
|
232
|
+
# Load environment exlicitly before loading specs to make it available in spec definition
|
233
|
+
require_relative '../lib/spectre/environment' unless cfg['exclude'].include? 'spectre/environment'
|
234
|
+
Spectre.configure(cfg)
|
235
|
+
|
236
|
+
|
237
|
+
###########################################
|
238
|
+
# Load Specs
|
239
|
+
###########################################
|
240
|
+
|
241
|
+
|
242
|
+
cfg['spec_patterns'].each do |pattern|
|
243
|
+
Dir.glob(pattern).each do|f|
|
244
|
+
require_relative File.join(Dir.pwd, f)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
###########################################
|
250
|
+
# List specs
|
251
|
+
###########################################
|
252
|
+
|
253
|
+
|
254
|
+
if 'list' == action
|
255
|
+
colors = [:blue, :magenta, :yellow, :green]
|
256
|
+
specs = Spectre.specs(cfg['specs'], cfg['tags'])
|
257
|
+
|
258
|
+
exit 1 unless specs.any?
|
259
|
+
|
260
|
+
counter = 0
|
261
|
+
|
262
|
+
specs.group_by { |x| x.subject }.each do |subject, spec_group|
|
263
|
+
spec_group.each do |spec|
|
264
|
+
tags = spec.tags.map { |x| '#' + x.to_s }.join ' '
|
265
|
+
desc = subject.desc
|
266
|
+
desc += ' - ' + spec.context.__desc + ' -' if spec.context.__desc
|
267
|
+
desc += ' ' + spec.desc
|
268
|
+
puts "[#{spec.name}]".send(colors[counter % colors.length]) + " #{desc} #{tags.cyan}"
|
269
|
+
end
|
270
|
+
|
271
|
+
counter += 1
|
272
|
+
end
|
273
|
+
|
274
|
+
exit 0
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
###########################################
|
279
|
+
# Run
|
280
|
+
###########################################
|
281
|
+
|
282
|
+
|
283
|
+
if 'run' == action
|
284
|
+
# Initialize logger
|
285
|
+
now = Time.now
|
286
|
+
|
287
|
+
cfg['log_file'] = cfg['log_file'].frmt(
|
288
|
+
{
|
289
|
+
shortdate: now.strftime('%Y-%m-%d'),
|
290
|
+
date: now.strftime('%Y-%m-%d_%H%M%S'),
|
291
|
+
timestamp: now.strftime('%s'),
|
292
|
+
subject: 'spectre',
|
293
|
+
})
|
294
|
+
|
295
|
+
log_dir = File.dirname cfg['log_file']
|
296
|
+
FileUtils.makedirs log_dir unless Dir.exists? log_dir
|
297
|
+
|
298
|
+
# Load Modules
|
299
|
+
|
300
|
+
cfg['modules']
|
301
|
+
.concat(cfg['include'])
|
302
|
+
.select { |mod| !cfg['exclude'].include? mod }
|
303
|
+
.each do |mod|
|
304
|
+
begin
|
305
|
+
mod_file = mod + '.rb'
|
306
|
+
spectre_lib_mod = File.join(File.dirname(__dir__), 'lib', mod_file)
|
307
|
+
|
308
|
+
if File.exists? mod_file
|
309
|
+
require_relative mod_file
|
310
|
+
|
311
|
+
elsif File.exists? spectre_lib_mod
|
312
|
+
require_relative spectre_lib_mod
|
313
|
+
|
314
|
+
else
|
315
|
+
require mod
|
316
|
+
end
|
317
|
+
rescue LoadError => e
|
318
|
+
puts "Unable to load module #{mod}. Check if the module exists or remove it from your spectre config:\n#{e.message}"
|
319
|
+
exit 1
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Load mixins
|
324
|
+
|
325
|
+
cfg['mixin_patterns'].each do |pattern|
|
326
|
+
Dir.glob(pattern).each do|f|
|
327
|
+
require_relative File.join(Dir.pwd, f)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
Spectre.configure(cfg)
|
332
|
+
|
333
|
+
Spectre::Logger.debug! if cfg['debug']
|
334
|
+
|
335
|
+
cfg['loggers'].each do |logger_name|
|
336
|
+
logger = Kernel.const_get(logger_name).new(cfg)
|
337
|
+
Spectre::Logger.add(logger)
|
338
|
+
end if cfg['loggers']
|
339
|
+
|
340
|
+
specs = Spectre.specs(cfg['specs'], cfg['tags'])
|
341
|
+
|
342
|
+
unless specs.any?
|
343
|
+
puts "No specs found in #{Dir.pwd}"
|
344
|
+
exit 1
|
345
|
+
end
|
346
|
+
|
347
|
+
run_infos = Spectre::Runner.new.run(specs)
|
348
|
+
|
349
|
+
cfg['reporters'].each do |reporter|
|
350
|
+
reporter = Kernel.const_get(reporter).new(cfg)
|
351
|
+
reporter.report(run_infos)
|
352
|
+
end
|
353
|
+
|
354
|
+
errors = run_infos.select { |x| nil != x.error or nil != x.failure }
|
355
|
+
|
356
|
+
exit 0 if cfg['ignore_failure'] or not errors.any?
|
357
|
+
|
358
|
+
exit 1
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
###########################################
|
363
|
+
# Envs
|
364
|
+
###########################################
|
365
|
+
|
366
|
+
|
367
|
+
if 'envs' == action
|
368
|
+
exit 1 unless envs.any?
|
369
|
+
puts envs.pretty
|
370
|
+
exit 0
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
###########################################
|
375
|
+
# Show
|
376
|
+
###########################################
|
377
|
+
|
378
|
+
|
379
|
+
if 'show' == action
|
380
|
+
puts cfg.pretty
|
381
|
+
exit 0
|
382
|
+
end
|
383
|
+
|
384
|
+
|
385
|
+
###########################################
|
386
|
+
# Dump
|
387
|
+
###########################################
|
388
|
+
|
389
|
+
|
390
|
+
if 'dump' == action
|
391
|
+
puts YAML.dump(cfg)
|
392
|
+
end
|
393
|
+
|
394
|
+
|
395
|
+
###########################################
|
396
|
+
# Init
|
397
|
+
###########################################
|
398
|
+
|
399
|
+
DEFAULT_SPECTRE_CFG = %{log_file: ./logs/spectre_<date>.log
|
400
|
+
env_patterns:
|
401
|
+
- './environments/**/*.env.yml'
|
402
|
+
env_partial_patterns:
|
403
|
+
- './environments/**/*.env.secret.yml'
|
404
|
+
spec_patterns:
|
405
|
+
- './specs/**/*.spec.rb'
|
406
|
+
mixin_patterns:
|
407
|
+
- '../common/**/*.mixin.rb'
|
408
|
+
- './mixins/**/*.mixin.rb'
|
409
|
+
resource_paths:
|
410
|
+
- '../common/resources'
|
411
|
+
- './resources'
|
412
|
+
}
|
413
|
+
|
414
|
+
|
415
|
+
DEFAULT_ENV_CFG = %{cert: &cert ./resources/<root_cert>.cer
|
416
|
+
http:
|
417
|
+
<http_client_name>:
|
418
|
+
base_url: http://localhost:5000/api/v1/
|
419
|
+
# basic_auth:
|
420
|
+
# username: <username>
|
421
|
+
# password: <password>
|
422
|
+
# keystone:
|
423
|
+
# url: https://<keystone_url>/main/v3/
|
424
|
+
# username: <username>
|
425
|
+
# password: <password>
|
426
|
+
# project: <project>
|
427
|
+
# domain: <domain>
|
428
|
+
# cert: *cert
|
429
|
+
# ssh:
|
430
|
+
# <ssh_client_name>:
|
431
|
+
# host: <hostname>
|
432
|
+
# username: <username>
|
433
|
+
# password: <password>
|
434
|
+
}
|
435
|
+
|
436
|
+
DEFAULT_ENV_SECRET_CFG = %{http:
|
437
|
+
<http_client_name>:
|
438
|
+
# basic_auth:
|
439
|
+
# username: <username>
|
440
|
+
# password: <password>
|
441
|
+
# keystone:
|
442
|
+
# username: <username>
|
443
|
+
# password: <password>
|
444
|
+
# ssh:
|
445
|
+
# <ssh_client_name>:
|
446
|
+
# username: <username>
|
447
|
+
# password: <password>
|
448
|
+
}
|
449
|
+
|
450
|
+
SAMPLE_SPEC = %[describe '<subject>' do
|
451
|
+
it 'does some http requests', tags: [:sample] do
|
452
|
+
log 'doing some http request'
|
453
|
+
|
454
|
+
http '<http_client_name>' do
|
455
|
+
auth 'basic'
|
456
|
+
# auth 'keystone'
|
457
|
+
method 'GET'
|
458
|
+
path 'path/to/resource'
|
459
|
+
param 'id', 4295118773
|
460
|
+
param 'foo', 'bar'
|
461
|
+
header 'X-Correlation-Id', '4c2367b1-bfee-4cc2-bdc5-ed17a6a9dd4b'
|
462
|
+
header 'Range', 'bytes=500-999'
|
463
|
+
json({
|
464
|
+
"message": "Hello Spectre!"
|
465
|
+
})
|
466
|
+
end
|
467
|
+
|
468
|
+
expect 'the response code to be 200' do
|
469
|
+
response.code.should_be 200
|
470
|
+
end
|
471
|
+
|
472
|
+
expect 'a message to exist' do
|
473
|
+
response.json.message.should_not_be nil
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
]
|
478
|
+
|
479
|
+
DEFAULT_GITIGNORE = %[*.code-workspace
|
480
|
+
logs/
|
481
|
+
reports/
|
482
|
+
**/environments/*.env.secret.yml
|
483
|
+
]
|
484
|
+
|
485
|
+
DEFAULT_GEMFILE = %[source 'https://rubygems.org'
|
486
|
+
|
487
|
+
gem 'spectre-core', '>= #{Spectre::VERSION}'
|
488
|
+
# gem 'spectre-mysql', '>= 1.0.0'
|
489
|
+
# gem 'spectre-ssh', '>= 1.0.0'
|
490
|
+
# gem 'spectre-ftp', '>= 1.0.0'
|
491
|
+
# gem 'spectre-curl', '>= 1.0.0'
|
492
|
+
# gem 'spectre-git', '>= 0.1.0'
|
493
|
+
]
|
494
|
+
|
495
|
+
if 'init' == action
|
496
|
+
DEFAULT_FILES = [
|
497
|
+
['./environments/default.env.yml', DEFAULT_ENV_CFG],
|
498
|
+
['./environments/default.env.secret.yml', DEFAULT_ENV_SECRET_CFG],
|
499
|
+
['./specs/sample.spec.rb', SAMPLE_SPEC],
|
500
|
+
['./spectre.yml', DEFAULT_SPECTRE_CFG],
|
501
|
+
['./.gitignore', DEFAULT_GITIGNORE],
|
502
|
+
['./Gemfile', DEFAULT_GEMFILE],
|
503
|
+
]
|
504
|
+
|
505
|
+
%w(environments logs specs).each do |dir_name|
|
506
|
+
Dir.mkdir(dir_name) unless File.directory? dir_name
|
507
|
+
end
|
508
|
+
|
509
|
+
DEFAULT_FILES.each do |file, content|
|
510
|
+
unless File.exists? file
|
511
|
+
File.write(file, content)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
exit 0
|
516
|
+
end
|