cem_acpt 0.8.7 → 0.9.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.
- 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/logging/formatter.rb +3 -3
- data/lib/cem_acpt/logging.rb +17 -1
- data/lib/cem_acpt/provision/terraform/linux.rb +1 -1
- 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
|