cem_acpt 0.8.8 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/spec.yml +0 -3
- data/Gemfile.lock +9 -1
- data/README.md +95 -13
- data/cem_acpt.gemspec +2 -1
- data/lib/cem_acpt/action_result.rb +8 -2
- data/lib/cem_acpt/actions.rb +153 -0
- data/lib/cem_acpt/bolt/cmd/base.rb +174 -0
- data/lib/cem_acpt/bolt/cmd/output.rb +315 -0
- data/lib/cem_acpt/bolt/cmd/task.rb +59 -0
- data/lib/cem_acpt/bolt/cmd.rb +22 -0
- data/lib/cem_acpt/bolt/errors.rb +49 -0
- data/lib/cem_acpt/bolt/helpers.rb +52 -0
- data/lib/cem_acpt/bolt/inventory.rb +62 -0
- data/lib/cem_acpt/bolt/project.rb +38 -0
- data/lib/cem_acpt/bolt/summary_results.rb +96 -0
- data/lib/cem_acpt/bolt/tasks.rb +181 -0
- data/lib/cem_acpt/bolt/tests.rb +415 -0
- data/lib/cem_acpt/bolt/yaml_file.rb +74 -0
- data/lib/cem_acpt/bolt.rb +142 -0
- data/lib/cem_acpt/cli.rb +6 -0
- data/lib/cem_acpt/config/base.rb +4 -0
- data/lib/cem_acpt/config/cem_acpt.rb +7 -1
- data/lib/cem_acpt/core_ext.rb +25 -0
- data/lib/cem_acpt/goss/api/action_response.rb +4 -0
- data/lib/cem_acpt/goss/api.rb +23 -25
- data/lib/cem_acpt/image_builder/provision_commands.rb +43 -0
- data/lib/cem_acpt/logging/formatter.rb +3 -3
- data/lib/cem_acpt/logging.rb +17 -1
- data/lib/cem_acpt/provision/terraform/linux.rb +2 -2
- data/lib/cem_acpt/test_data.rb +2 -0
- data/lib/cem_acpt/test_runner/log_formatter/base.rb +73 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +65 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +54 -0
- data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +64 -0
- data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +17 -30
- data/lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb +31 -0
- data/lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb +35 -0
- data/lib/cem_acpt/test_runner/log_formatter.rb +17 -5
- data/lib/cem_acpt/test_runner/test_results.rb +150 -0
- data/lib/cem_acpt/test_runner.rb +153 -53
- data/lib/cem_acpt/utils/files.rb +189 -0
- data/lib/cem_acpt/utils/finalizer_queue.rb +73 -0
- data/lib/cem_acpt/utils/shell.rb +13 -4
- data/lib/cem_acpt/version.rb +1 -1
- data/sample_config.yaml +13 -0
- metadata +41 -5
- data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +0 -33
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative 'cmd'
|
5
|
+
require_relative 'errors'
|
6
|
+
require_relative '../core_ext'
|
7
|
+
require_relative '../logging'
|
8
|
+
require_relative '../utils/shell'
|
9
|
+
|
10
|
+
module CemAcpt
|
11
|
+
module Bolt
|
12
|
+
using CemAcpt::CoreExt::ExtendedArray
|
13
|
+
|
14
|
+
# Provides a wrapper for Bolt tasks that holds data and provides methods for interacting with them
|
15
|
+
# This should probably be genericized as ObjectWrapper to account for Bolt plans, as well
|
16
|
+
class TaskWrapper
|
17
|
+
include CemAcpt::Logging
|
18
|
+
attr_reader :full_name, :name, :description, :module_name, :last_cmd_executed
|
19
|
+
|
20
|
+
def initialize(config, task_array, project: nil, inventory: nil)
|
21
|
+
@config = config
|
22
|
+
@full_name = task_array[0]
|
23
|
+
@description = task_array[1]
|
24
|
+
@module_name = @full_name.split('::').first
|
25
|
+
@name = @full_name.split('::').last
|
26
|
+
@show_cmd = CemAcpt::Bolt::Cmd::TaskShow.new(@config, full_name, project: project)
|
27
|
+
@run_cmd = CemAcpt::Bolt::Cmd::TaskRun.new(@config, full_name, project: project, inventory: inventory)
|
28
|
+
@last_cmd_executed = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
"#<#{self.class}:\"#{full_name}\">"
|
33
|
+
end
|
34
|
+
|
35
|
+
def show(**params)
|
36
|
+
logger.verbose('CemAcpt::Bolt::TaskWrapper') { "Executing task show with params: #{params}" }
|
37
|
+
@last_cmd_executed = @show_cmd
|
38
|
+
show_cmd.run(**params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def run(**params)
|
42
|
+
logger.verbose('CemAcpt::Bolt::TaskWrapper') { "Executing task run with params: #{params}" }
|
43
|
+
@last_cmd_executed = @run_cmd
|
44
|
+
run_cmd.run(**params)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :show_cmd, :run_cmd
|
50
|
+
end
|
51
|
+
|
52
|
+
# Holds and manages the list of tasks in the current bolt project
|
53
|
+
class TaskList
|
54
|
+
include CemAcpt::Logging
|
55
|
+
include CemAcpt::Bolt::Helpers
|
56
|
+
|
57
|
+
attr_reader :metadata
|
58
|
+
|
59
|
+
def initialize(config, inventory = nil, project = nil)
|
60
|
+
@config = config
|
61
|
+
@inventory = inventory
|
62
|
+
@project = project
|
63
|
+
@tasks = []
|
64
|
+
@metadata = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def unfiltered
|
68
|
+
find_all_tasks
|
69
|
+
end
|
70
|
+
|
71
|
+
def tasks
|
72
|
+
return @tasks unless @tasks.nil? || @tasks.empty?
|
73
|
+
|
74
|
+
@tasks = filter_tasks(unfiltered)
|
75
|
+
@tasks
|
76
|
+
end
|
77
|
+
|
78
|
+
def module_pattern
|
79
|
+
@module_pattern ||= get_pattern('bolt.tasks.module_pattern')
|
80
|
+
end
|
81
|
+
|
82
|
+
def name_filter
|
83
|
+
@name_pattern ||= get_pattern('bolt.tasks.name_filter', default: %r{^$})
|
84
|
+
end
|
85
|
+
|
86
|
+
def ignore
|
87
|
+
@ignore ||= @config.get('bolt.tasks.ignore') || []
|
88
|
+
end
|
89
|
+
|
90
|
+
def only
|
91
|
+
@only ||= @config.get('bolt.tasks.only') || []
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_missing(method, *args, **kwargs, &block)
|
95
|
+
if tasks.respond_to?(method)
|
96
|
+
tasks.send(method, *args, **kwargs, &block)
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def respond_to_missing?(method, include_private = false)
|
103
|
+
tasks.respond_to?(method, include_private) || super
|
104
|
+
end
|
105
|
+
|
106
|
+
def inspect
|
107
|
+
tasks.inspect
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def get_pattern(config_key, default: %r{.*})
|
113
|
+
pat = @config.get(config_key)
|
114
|
+
return default unless pat
|
115
|
+
|
116
|
+
Regexp.new(pat)
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_all_tasks
|
120
|
+
logger.debug('CemAcpt::Bolt::TaskList') { 'Finding all Bolt tasks...' }
|
121
|
+
out = CemAcpt::Bolt::Cmd::TaskShow.new(@config, project: @project).run(strict: false)
|
122
|
+
if out.error?
|
123
|
+
logger.error('CemAcpt::Bolt::TaskList') { "Error finding Bolt tasks:\n#{out}" }
|
124
|
+
logger.debug('CemAcpt::Bolt::TaskList') { "Backtrace:\n#{out.error.backtrace.join("\n")}" } if out.error.respond_to?(:backtrace)
|
125
|
+
raise out.error if out.error.is_a?(StandardError)
|
126
|
+
raise CemAcpt::Bolt::BoltActionError.new("Error finding Bolt tasks: #{out.error.msg}",
|
127
|
+
out.error,
|
128
|
+
out.error.kind,
|
129
|
+
out.error.details)
|
130
|
+
|
131
|
+
end
|
132
|
+
out.to_h['tasks'].map { |task_array| TaskWrapper.new(@config, task_array) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def filter_tasks(td_array)
|
136
|
+
@metadata[:filter] = {}
|
137
|
+
td_array.select do |t|
|
138
|
+
@metadata[:filter][t.full_name] = []
|
139
|
+
unless module_pattern.match?(t.module_name)
|
140
|
+
@metadata[:filter][t.full_name] << {
|
141
|
+
rule: 'module_pattern',
|
142
|
+
value: t.module_name,
|
143
|
+
pattern: module_pattern,
|
144
|
+
}
|
145
|
+
next
|
146
|
+
end
|
147
|
+
if name_filter.match?(t.name)
|
148
|
+
@metadata[:filter][t.full_name] << {
|
149
|
+
rule: 'name_filter',
|
150
|
+
value: t.name,
|
151
|
+
pattern: name_filter,
|
152
|
+
}
|
153
|
+
next
|
154
|
+
end
|
155
|
+
|
156
|
+
only_check = only.empty? || only.any? { |o| t.full_name.match?(o) }
|
157
|
+
ignore_check = ignore.empty? || ignore.none? { |i| t.full_name.match?(i) }
|
158
|
+
if only_check && ignore_check
|
159
|
+
true
|
160
|
+
else
|
161
|
+
unless only_check
|
162
|
+
@metadata[:filter][t.full_name] << {
|
163
|
+
rule: 'only',
|
164
|
+
value: t.name,
|
165
|
+
pattern: only,
|
166
|
+
}
|
167
|
+
end
|
168
|
+
unless ignore_check
|
169
|
+
@metadata[:filter][t.full_name] << {
|
170
|
+
rule: 'ignore',
|
171
|
+
value: t.name,
|
172
|
+
pattern: ignore,
|
173
|
+
}
|
174
|
+
end
|
175
|
+
false
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,415 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require_relative 'errors'
|
5
|
+
require_relative 'tasks'
|
6
|
+
require_relative '../core_ext'
|
7
|
+
require_relative '../logging'
|
8
|
+
|
9
|
+
module CemAcpt
|
10
|
+
module Bolt
|
11
|
+
module Tests
|
12
|
+
using CemAcpt::CoreExt::ExtendedArray
|
13
|
+
|
14
|
+
# Provides an abstraction for the specific Bolt test data
|
15
|
+
class TestData
|
16
|
+
attr_reader :prop_validators, :params, :status
|
17
|
+
|
18
|
+
def initialize(**prop_validators)
|
19
|
+
@prop_validators = { params: {}, status: 'success' }.merge(prop_validators.transform_keys(&:to_sym))
|
20
|
+
@params = @prop_validators[:params]
|
21
|
+
@status = @prop_validators[:status]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
prop_validators
|
26
|
+
end
|
27
|
+
|
28
|
+
def validation_properties
|
29
|
+
@validation_properties ||= prop_validators.keys
|
30
|
+
end
|
31
|
+
|
32
|
+
# Compares prop_validators with another object that implements any of the properties as methods.
|
33
|
+
# @param other [Object] the object to compare against
|
34
|
+
# @return [Array<Hash>] an array of the hashes representing the results of the validation
|
35
|
+
# @raise [ArgumentError] if other does not implement any of the properties
|
36
|
+
def validate_props(other)
|
37
|
+
if (other.methods & validation_properties).empty?
|
38
|
+
raise ArgumentError, "Object does not implement any of the properties: #{validation_properties}"
|
39
|
+
end
|
40
|
+
|
41
|
+
prop_validators.each_with_object([]) do |(k, v), ary|
|
42
|
+
unless other.respond_to?(k)
|
43
|
+
ary << validation_hash(k, 'skipped', 'property_not_implemented', v, nil)
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
other_val = other.send(k)
|
48
|
+
begin
|
49
|
+
case v
|
50
|
+
when String
|
51
|
+
ary << validation_hash(k, result_from_bool(other_val == v), 'string_eq', v, other_val)
|
52
|
+
when Hash
|
53
|
+
v.transform_keys!(&:to_sym)
|
54
|
+
unknown_validators_val = validate_validators(v, :match, :not_match)
|
55
|
+
match_val = validate_match(k, v[:match], other_val)
|
56
|
+
not_match_val = validate_match(k, v[:not_match], other_val, negate: true)
|
57
|
+
[match_val, not_match_val, unknown_validators_val].compact.each { |h| ary << h }
|
58
|
+
else
|
59
|
+
ary << validation_hash(k, result_from_bool(other_val == v), 'simple_eq', v, other_val)
|
60
|
+
end
|
61
|
+
rescue StandardError => e
|
62
|
+
ary << validation_hash(k, 'failure', 'ruby_error', e, nil)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def validate_match(prop, this_v, other_v, negate: false)
|
70
|
+
return if this_v.nil?
|
71
|
+
|
72
|
+
res = if other_v.is_a?(Hash)
|
73
|
+
JSON.generate(other_v).match?(this_v)
|
74
|
+
else
|
75
|
+
other_v.match?(this_v)
|
76
|
+
end
|
77
|
+
res = !res if negate
|
78
|
+
validation_hash(prop, result_from_bool(res), 'match', other_v, this_v)
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_validators(this_v, *keys)
|
82
|
+
return if this_v.nil?
|
83
|
+
|
84
|
+
unknown_keys = this_v.keys - keys.flatten.map(&:to_sym)
|
85
|
+
return if unknown_keys.empty?
|
86
|
+
|
87
|
+
validation_hash(:validators, 'failure', 'unknown_validators', this_v, unknown_keys)
|
88
|
+
end
|
89
|
+
|
90
|
+
def result_from_bool(bool)
|
91
|
+
bool ? 'success' : 'failure'
|
92
|
+
end
|
93
|
+
|
94
|
+
def validation_hash(prop, result, validator, validation_v, other_v)
|
95
|
+
{ prop: prop, result: result, validator: validator, validation_value: validation_v, other_value: other_v }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Contains the results of a single Bolt task test
|
100
|
+
class TestResult
|
101
|
+
attr_reader :name, :command_result, :validation_results, :details
|
102
|
+
|
103
|
+
def initialize(name, command_result, validation_results, **details)
|
104
|
+
@name = name
|
105
|
+
@command_result = command_result
|
106
|
+
@validation_results = validation_results
|
107
|
+
@details = details
|
108
|
+
end
|
109
|
+
|
110
|
+
def failed_validation_results
|
111
|
+
@failed_validation_results ||= validation_results.select { |vr| vr[:result] == 'failure' }
|
112
|
+
end
|
113
|
+
|
114
|
+
def skipped_validation_results
|
115
|
+
@skipped_validation_results ||= validation_results.select { |vr| vr[:result] == 'skipped' }
|
116
|
+
end
|
117
|
+
|
118
|
+
def success_validation_results
|
119
|
+
@success_validation_results ||= validation_results.select { |vr| vr[:result] == 'success' }
|
120
|
+
end
|
121
|
+
|
122
|
+
def action
|
123
|
+
@action ||= if @command_result.is_a?(CemAcpt::Bolt::BoltActionError)
|
124
|
+
@command_result.bolt_action
|
125
|
+
elsif @command_result.respond_to?(:action)
|
126
|
+
@command_result.action
|
127
|
+
else
|
128
|
+
'unknown'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def target
|
133
|
+
@target ||= if @command_result.is_a?(CemAcpt::Bolt::BoltActionError)
|
134
|
+
@command_result.bolt_target
|
135
|
+
elsif @command_result.respond_to?(:target)
|
136
|
+
@command_result.target
|
137
|
+
else
|
138
|
+
'unknown'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def object
|
143
|
+
@object ||= if @command_result.is_a?(CemAcpt::Bolt::BoltActionError)
|
144
|
+
@command_result.bolt_object
|
145
|
+
elsif @command_result.respond_to?(:object)
|
146
|
+
@command_result.object
|
147
|
+
else
|
148
|
+
'unknown'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def success?
|
153
|
+
@validation_results.none? { |vr| vr[:result] == 'failure' }
|
154
|
+
end
|
155
|
+
|
156
|
+
def status
|
157
|
+
@status ||= success? ? 'success' : 'failure'
|
158
|
+
end
|
159
|
+
|
160
|
+
def error?
|
161
|
+
@command_result.is_a?(CemAcpt::Bolt::BoltActionError)
|
162
|
+
end
|
163
|
+
|
164
|
+
def error
|
165
|
+
@command_result if error?
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_h
|
169
|
+
{
|
170
|
+
name: name,
|
171
|
+
success: success?,
|
172
|
+
command_result: command_result.to_h,
|
173
|
+
validation_results: validation_results,
|
174
|
+
details: details,
|
175
|
+
}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Contains the results of running a Bolt task test
|
180
|
+
class TestResults
|
181
|
+
attr_reader :test_name, :results
|
182
|
+
|
183
|
+
def initialize(test_name, results = [])
|
184
|
+
@test_name = test_name
|
185
|
+
@results = results
|
186
|
+
end
|
187
|
+
|
188
|
+
def success?
|
189
|
+
@results.all? { |r| r.success? }
|
190
|
+
end
|
191
|
+
|
192
|
+
def status
|
193
|
+
@status ||= success? ? 'success' : 'failure'
|
194
|
+
end
|
195
|
+
|
196
|
+
def action
|
197
|
+
@action ||= @results.empty? ? 'unknown' : @results.first.action
|
198
|
+
end
|
199
|
+
|
200
|
+
def target
|
201
|
+
@target ||= @results.empty? ? 'unknown' : @results.first.target
|
202
|
+
end
|
203
|
+
|
204
|
+
def object
|
205
|
+
@object ||= @results.empty? ? 'unknown' : @results.first.object
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_h
|
209
|
+
{
|
210
|
+
test_name: test_name,
|
211
|
+
success: success?,
|
212
|
+
results: @results.map(&:to_h),
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
217
|
+
if @results.respond_to?(method_name)
|
218
|
+
@results.send(method_name, *args, **kwargs, &block)
|
219
|
+
else
|
220
|
+
super
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def respond_to_missing?(method_name, include_private = false)
|
225
|
+
@results.respond_to?(method_name, include_private) || super
|
226
|
+
end
|
227
|
+
|
228
|
+
def inspect
|
229
|
+
"#<#{self.class}:#{object_id.to_s(16)} with #{results.count} result(s)>"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Provides an interface for running tests against Bolt tasks
|
234
|
+
class Test
|
235
|
+
include CemAcpt::Logging
|
236
|
+
|
237
|
+
attr_reader :name, :data, :object, :groups, :command_result, :result
|
238
|
+
|
239
|
+
def initialize(name, data, object, *groups)
|
240
|
+
@name = name
|
241
|
+
@data = data.is_a?(Hash) ? TestData.new(**data) : TestData.new
|
242
|
+
@object = object
|
243
|
+
@groups = groups
|
244
|
+
@command_result = nil
|
245
|
+
@result = TestResults.new(name)
|
246
|
+
@validation_results = {}
|
247
|
+
end
|
248
|
+
|
249
|
+
# Groups in this context refers to the acceptance test names. Because the acceptance tests act like logical
|
250
|
+
# groups, we refer to them as groups here to avoid confusion with the Bolt tests.
|
251
|
+
def add_group(group)
|
252
|
+
@groups << group unless @groups.include?(group)
|
253
|
+
end
|
254
|
+
|
255
|
+
def run
|
256
|
+
logger.debug('CemAcpt::Bolt::Tests::Test') { "Running test #{name}" }
|
257
|
+
@command_result = @object.run(**@data.params)
|
258
|
+
validate!
|
259
|
+
rescue StandardError => e
|
260
|
+
new_wrapped_error("Error running test #{name}", e)
|
261
|
+
end
|
262
|
+
|
263
|
+
def to_s
|
264
|
+
"#<#{self.class}:#{object_id.to_s(16)} #{name},#{object},#{groups.join(',')}>"
|
265
|
+
end
|
266
|
+
|
267
|
+
def inspect
|
268
|
+
to_s
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
# Loops through the results of the Bolt task and validates the properties of each result.
|
274
|
+
# @return [TaskResults] the results of the validation
|
275
|
+
def validate!
|
276
|
+
logger.debug('CemAcpt::Bolt::Tests::Test') { "Validating test #{name}" }
|
277
|
+
begin
|
278
|
+
@command_result.results.each do |r|
|
279
|
+
prop_vals = @data.validate_props(r)
|
280
|
+
@validation_results[r] = prop_vals
|
281
|
+
@result << TestResult.new(name, r, prop_vals, test_groups: groups)
|
282
|
+
end
|
283
|
+
rescue StandardError => e
|
284
|
+
logger.debug('CemAcpt::Bolt::Tests::Test') { "Error validating command result for test #{name}:\n#{e}" }
|
285
|
+
wrapped_error = new_wrapped_error("Error validating test #{name}", e)
|
286
|
+
@result << TestResult.new(
|
287
|
+
name,
|
288
|
+
wrapped_error,
|
289
|
+
[],
|
290
|
+
test_groups: groups,
|
291
|
+
backtrace: e.backtrace,
|
292
|
+
)
|
293
|
+
end
|
294
|
+
rescue StandardError => e
|
295
|
+
logger.debug('CemAcpt::Bolt::Tests::Test') { "Error validating test #{name}:\n#{e}" }
|
296
|
+
wrapped_error = new_wrapped_error("Error validating test #{name}", e)
|
297
|
+
@result << TestResult.new(name, wrapped_error, [], test_groups: groups)
|
298
|
+
end
|
299
|
+
|
300
|
+
def new_wrapped_error(errmsg, original_err = nil)
|
301
|
+
last_cmd = @object.last_cmd_executed
|
302
|
+
cmd_family = last_cmd.nil? ? 'unknown' : last_cmd.command_family
|
303
|
+
CemAcpt::Bolt::BoltActionError.new(errmsg, original_err, cmd_family, last_cmd)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Loads test data and provides an interface for running tests against Bolt tasks
|
308
|
+
class TestList
|
309
|
+
include CemAcpt::Logging
|
310
|
+
attr_accessor :config, :run_data, :inventory, :project
|
311
|
+
attr_reader :test_data, :tasks, :tests
|
312
|
+
|
313
|
+
def initialize(config = nil, run_data = nil, inventory = nil, project = nil)
|
314
|
+
@config = config
|
315
|
+
@run_data = run_data
|
316
|
+
@inventory = inventory
|
317
|
+
@project = project
|
318
|
+
@test_data = nil
|
319
|
+
@tasks = []
|
320
|
+
@tests = []
|
321
|
+
@loaded = false
|
322
|
+
@setup = false
|
323
|
+
end
|
324
|
+
|
325
|
+
# Loads the test data from the Bolt test files specified in the run data
|
326
|
+
def load!
|
327
|
+
return if loaded?
|
328
|
+
raise 'Run data has not been set, assign it to the #run_data attribute first' unless @run_data
|
329
|
+
|
330
|
+
@test_data = load_test_data(@run_data)
|
331
|
+
@loaded = true
|
332
|
+
end
|
333
|
+
|
334
|
+
def loaded?
|
335
|
+
@loaded
|
336
|
+
end
|
337
|
+
|
338
|
+
# Sets up the tests by loading the Bolt tasks and creating a TestWrapper for each test
|
339
|
+
def setup!
|
340
|
+
load! unless loaded?
|
341
|
+
@tasks = CemAcpt::Bolt::TaskList.new(@config)
|
342
|
+
@tasks.tasks # Ensure tasks are loaded
|
343
|
+
logger.verbose('CemAcpt::Bolt::Tests') { "TaskList metadata:\n#{@tasks.metadata.to_yaml}" }
|
344
|
+
@tests = new_test_array(@tasks, @test_data)
|
345
|
+
@setup = true
|
346
|
+
end
|
347
|
+
|
348
|
+
def setup?
|
349
|
+
@setup
|
350
|
+
end
|
351
|
+
|
352
|
+
def to_a
|
353
|
+
@tests
|
354
|
+
end
|
355
|
+
|
356
|
+
def split_into_groups(num_groups)
|
357
|
+
raise 'Tests have not been setup, call the #setup! method first' unless setup?
|
358
|
+
|
359
|
+
@tests.split_into_groups(num_groups)
|
360
|
+
end
|
361
|
+
|
362
|
+
def length
|
363
|
+
@tests.length
|
364
|
+
end
|
365
|
+
alias count length
|
366
|
+
alias size length
|
367
|
+
|
368
|
+
def to_s
|
369
|
+
"#<#{self.class}:#{object_id.to_s(16)} #{tests.join(',')}>"
|
370
|
+
end
|
371
|
+
|
372
|
+
def inspect
|
373
|
+
to_s
|
374
|
+
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
def load_test_data(run_data)
|
379
|
+
run_data[:test_data].each_with_object({}) do |tdata, h|
|
380
|
+
test_name = tdata[:test_name]
|
381
|
+
next if h.key?(test_name) || tdata[:bolt_test].nil?
|
382
|
+
|
383
|
+
logger.debug('CemAcpt::Bolt::Tests') { "Loading test data for test #{test_name}" }
|
384
|
+
begin
|
385
|
+
h[test_name] = YAML.safe_load_file(tdata[:bolt_test])
|
386
|
+
rescue Psych::SyntaxError, NoMethodError
|
387
|
+
h[test_name] = YAML.safe_load(File.read(tdata[:bolt_test]))
|
388
|
+
end
|
389
|
+
logger.debug('CemAcpt::Bolt::Tests') { "Loaded test data for test #{test_name}" }
|
390
|
+
logger.verbose('CemAcpt::Bolt::Tests') { "Test data for test #{test_name}:\n#{h[test_name].to_yaml}" }
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def new_test_array(tasks, test_data)
|
395
|
+
tw_created = {}
|
396
|
+
logger.debug('CemAcpt::Bolt::Tests') { 'Creating tests for Bolt tasks' }
|
397
|
+
tasks.each do |task|
|
398
|
+
test_data.each do |group, tdata|
|
399
|
+
next unless @config.get('tests').include?(group)
|
400
|
+
|
401
|
+
if tw_created.key?(task.full_name)
|
402
|
+
tw_created[task.full_name].add_group(group)
|
403
|
+
logger.debug('CemAcpt::Bolt::Tests') { "Added group #{group} to test for task #{task.full_name}" }
|
404
|
+
else
|
405
|
+
tw_created[task.full_name] = Test.new(task.full_name, tdata[task.full_name], task, group)
|
406
|
+
logger.debug('CemAcpt::Bolt::Tests') { "Created test for task #{task.full_name}" }
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
tw_created.values
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../logging'
|
4
|
+
require_relative '../utils/files'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
module Bolt
|
8
|
+
# Provides an abstraction for a YAML file
|
9
|
+
# Used as a base class for other YAML file abstractions
|
10
|
+
class YamlFile
|
11
|
+
include CemAcpt::Logging
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
@path = path
|
17
|
+
@hash = {}
|
18
|
+
@saved_hash = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_h
|
22
|
+
@hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def load!
|
26
|
+
logger.debug(self.class.to_s) { "Loading #{path}..." }
|
27
|
+
@saved_hash = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
|
28
|
+
logger.verbose(self.class.to_s) { "Loaded #{path} contents: #{@saved_hash}" }
|
29
|
+
@hash = @saved_hash.dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def save!
|
33
|
+
return if latest_saved?
|
34
|
+
|
35
|
+
logger.debug(self.class.to_s) { 'Saving file...' }
|
36
|
+
CemAcpt::Utils::Files.delete(path, log_prefix: self.class.to_s) if File.exist?(path)
|
37
|
+
logger.debug(self.class.to_s) { "Creating #{path}..." }
|
38
|
+
logger.verbose(self.class.to_s) { "New #{path} contents: #{@hash}" }
|
39
|
+
CemAcpt::Utils::Files.write(path, @hash, log_prefix: self.class.to_s)
|
40
|
+
@saved_hash = @hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete!
|
44
|
+
CemAcpt::Utils::Files.delete(path, log_prefix: self.class.to_s)
|
45
|
+
@saved_hash = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def latest_saved?
|
49
|
+
(@hash == @saved_hash) || eq_to_disk?
|
50
|
+
end
|
51
|
+
|
52
|
+
def eq_to_disk?
|
53
|
+
return false unless File.exist?(path)
|
54
|
+
|
55
|
+
disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
|
56
|
+
@hash.sort.to_h == disk_contents.sort.to_h
|
57
|
+
end
|
58
|
+
|
59
|
+
def gte_to_disk?
|
60
|
+
return false unless File.exist?(path)
|
61
|
+
|
62
|
+
disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
|
63
|
+
@hash.sort.to_h >= disk_contents.sort.to_h
|
64
|
+
end
|
65
|
+
|
66
|
+
def lte_to_disk?
|
67
|
+
return false unless File.exist?(path)
|
68
|
+
|
69
|
+
disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
|
70
|
+
@hash.sort.to_h <= disk_contents.sort.to_h
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|