cem_acpt 0.2.6-universal-java-17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +93 -0
- data/README.md +150 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cem_acpt.gemspec +39 -0
- data/exe/cem_acpt +84 -0
- data/lib/cem_acpt/bootstrap/bootstrapper.rb +206 -0
- data/lib/cem_acpt/bootstrap/operating_system/rhel_family.rb +129 -0
- data/lib/cem_acpt/bootstrap/operating_system.rb +17 -0
- data/lib/cem_acpt/bootstrap.rb +12 -0
- data/lib/cem_acpt/context.rb +153 -0
- data/lib/cem_acpt/core_extensions.rb +108 -0
- data/lib/cem_acpt/image_name_builder.rb +104 -0
- data/lib/cem_acpt/logging.rb +351 -0
- data/lib/cem_acpt/platform/base/cmd.rb +71 -0
- data/lib/cem_acpt/platform/base.rb +78 -0
- data/lib/cem_acpt/platform/gcp/cmd.rb +345 -0
- data/lib/cem_acpt/platform/gcp/compute.rb +332 -0
- data/lib/cem_acpt/platform/gcp.rb +85 -0
- data/lib/cem_acpt/platform/vmpooler.rb +24 -0
- data/lib/cem_acpt/platform.rb +103 -0
- data/lib/cem_acpt/puppet_helpers.rb +39 -0
- data/lib/cem_acpt/rspec_utils.rb +242 -0
- data/lib/cem_acpt/shared_objects.rb +537 -0
- data/lib/cem_acpt/spec_helper_acceptance.rb +184 -0
- data/lib/cem_acpt/test_data.rb +146 -0
- data/lib/cem_acpt/test_runner/run_handler.rb +187 -0
- data/lib/cem_acpt/test_runner/runner.rb +210 -0
- data/lib/cem_acpt/test_runner/runner_result.rb +103 -0
- data/lib/cem_acpt/test_runner.rb +10 -0
- data/lib/cem_acpt/utils.rb +144 -0
- data/lib/cem_acpt/version.rb +5 -0
- data/lib/cem_acpt.rb +34 -0
- data/sample_config.yaml +58 -0
- metadata +218 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CemAcpt
|
4
|
+
require_relative 'shared_objects'
|
5
|
+
|
6
|
+
# This methods is used in spec_helper_acceptance.rb to set up
|
7
|
+
# baseline configurations used in acceptance tests.
|
8
|
+
def self.configure_spec_helper!
|
9
|
+
require 'serverspec'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.include CemAcpt::SpecHelperAcceptance
|
14
|
+
config.extend CemAcpt::SpecHelperAcceptance
|
15
|
+
config.add_setting :acpt_test_data, default: {}
|
16
|
+
config.add_setting :acpt_node_inventory
|
17
|
+
config.threadsafe = true
|
18
|
+
config.color_mode = :off
|
19
|
+
config.fail_fast = false
|
20
|
+
end
|
21
|
+
|
22
|
+
node_inventory = NodeInventory.new
|
23
|
+
node_inventory.load
|
24
|
+
RSpec.configuration.acpt_node_inventory = node_inventory
|
25
|
+
end
|
26
|
+
|
27
|
+
# This module provides methods used in accpetance tests.
|
28
|
+
module SpecHelperAcceptance
|
29
|
+
# This method must be used inside of a `describe` block as the first
|
30
|
+
# statement. This prepares the ServerSpec configuration for the test node
|
31
|
+
# and sets up the test data.
|
32
|
+
def initialize_test_environment!
|
33
|
+
test_file = caller_locations.first.path
|
34
|
+
|
35
|
+
node_name, node_data = RSpec.configuration.acpt_node_inventory.get_by_property('test_data.test_file', test_file)
|
36
|
+
raise "Failed to get node data for node #{node_name}" unless node_data
|
37
|
+
raise "Node data format is incorrect: #{node_data}" unless node_data.is_a?(Hash)
|
38
|
+
|
39
|
+
backend = nil
|
40
|
+
host = nil
|
41
|
+
ssh_options = nil
|
42
|
+
puppet_path = nil
|
43
|
+
|
44
|
+
# Set remote communication variables based on transport type
|
45
|
+
case node_data[:transport]
|
46
|
+
when /ssh/
|
47
|
+
backend = :ssh
|
48
|
+
host = node_data[:node_name]
|
49
|
+
ssh_options = node_data[:ssh_opts]
|
50
|
+
sudo_options = '-n -u root -i'
|
51
|
+
puppet_path = '/opt/puppetlabs/bin/puppet'
|
52
|
+
when /winrm/
|
53
|
+
backend = :winrm
|
54
|
+
puppet_path = 'C:\Program Files\Puppet Labs\Puppet\bin\puppet.bat'
|
55
|
+
else
|
56
|
+
raise "Unknown transport: #{node[:transport]}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set serverspec transport options and host for remote communication.
|
60
|
+
set :backend, backend
|
61
|
+
set :host, host
|
62
|
+
set(:ssh_options, ssh_options) if ssh_options
|
63
|
+
set(:sudo_options, sudo_options) if sudo_options
|
64
|
+
set(:os, family: 'windows') if backend == :winrm
|
65
|
+
|
66
|
+
# Get the command provider from the node's platform
|
67
|
+
# We add this as a RSpec config option so that we can use it in
|
68
|
+
# other functions.
|
69
|
+
require 'cem_acpt/platform'
|
70
|
+
|
71
|
+
acpt_test_data = {
|
72
|
+
test_file: test_file,
|
73
|
+
node_name: node_name,
|
74
|
+
node_data: node_data,
|
75
|
+
backend: backend,
|
76
|
+
platform: CemAcpt::Platform.get(node_data[:platform]),
|
77
|
+
puppet_path: puppet_path,
|
78
|
+
}
|
79
|
+
acpt_test_data[:host] = host if host
|
80
|
+
RSpec.configuration.acpt_test_data = acpt_test_data
|
81
|
+
end
|
82
|
+
|
83
|
+
# This method formats Puppet Apply options
|
84
|
+
def puppet_apply_options(opts = {})
|
85
|
+
if [opts[:catch_changes], opts[:expect_changes], opts[:catch_failures], opts[:expect_failures]].compact.length > 1
|
86
|
+
raise ArgumentError,
|
87
|
+
'Please specify only one of "catch_changes", "expect_changes", "catch_failures", or "expect_failures"'
|
88
|
+
end
|
89
|
+
|
90
|
+
apply_opts = {}.merge(opts)
|
91
|
+
|
92
|
+
if opts[:catch_changes]
|
93
|
+
apply_opts[:detailed_exit_codes] = true
|
94
|
+
apply_opts[:acceptable_exit_codes] = [0]
|
95
|
+
elsif opts[:catch_failures]
|
96
|
+
apply_opts[:detailed_exit_codes] = true
|
97
|
+
apply_opts[:acceptable_exit_codes] = [0, 2]
|
98
|
+
elsif opts[:expect_failures]
|
99
|
+
apply_opts[:detailed_exit_codes] = true
|
100
|
+
apply_opts[:acceptable_exit_codes] = [1, 4, 6]
|
101
|
+
elsif opts[:expect_changes]
|
102
|
+
apply_opts[:detailed_exit_codes] = true
|
103
|
+
apply_opts[:acceptable_exit_codes] = [2]
|
104
|
+
else
|
105
|
+
apply_opts[:detailed_exit_codes] = false
|
106
|
+
apply_opts[:acceptable_exit_codes] = [0]
|
107
|
+
end
|
108
|
+
apply_opts
|
109
|
+
end
|
110
|
+
|
111
|
+
# This methods handles formatting the output from Puppet apply.
|
112
|
+
def handle_puppet_apply_output(result, apply_opts)
|
113
|
+
exit_code = result.exitstatus
|
114
|
+
output = result.to_s
|
115
|
+
if apply_opts[:catch_changes] && !apply_opts[:acceptable_exit_codes].include?(exit_code)
|
116
|
+
failure = <<~ERROR
|
117
|
+
Apply manifest expected no changes. Puppet Apply returned exit code #{exit_code}
|
118
|
+
====== Start output of Puppet apply with unexpected changes ======
|
119
|
+
#{output}
|
120
|
+
====== End output of Puppet apply with unexpected changes ======
|
121
|
+
ERROR
|
122
|
+
raise failure
|
123
|
+
elsif !apply_opts[:acceptable_exit_codes].include?(exit_code)
|
124
|
+
failure = <<~ERROR
|
125
|
+
Apply manifest failed with exit code #{exit_code} (expected: #{apply_opts[:acceptable_exit_codes]})
|
126
|
+
====== Start output of failed Puppet apply ======
|
127
|
+
#{output}
|
128
|
+
====== End output of failed Puppet apply ======
|
129
|
+
ERROR
|
130
|
+
raise failure
|
131
|
+
end
|
132
|
+
|
133
|
+
yield result if block_given?
|
134
|
+
|
135
|
+
if ENV['RSPEC_DEBUG']
|
136
|
+
run_result = <<~RUNRES
|
137
|
+
apply manifest succeded with status #{exit_code}
|
138
|
+
===== Start output of successful Puppet apply ======
|
139
|
+
#{output}
|
140
|
+
===== End output of successful Puppet apply ======
|
141
|
+
RUNRES
|
142
|
+
puts run_result
|
143
|
+
end
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
# This method runs a shell command on the test node.
|
148
|
+
def run_shell(cmd, opts = {})
|
149
|
+
cmd = cmd.join(' ') if cmd.is_a?(Array)
|
150
|
+
|
151
|
+
host = RSpec.configuration.acpt_test_data[:node_name]
|
152
|
+
opts[:ssh_opts] = RSpec.configuration.acpt_test_data[:node_data][:ssh_opts]
|
153
|
+
RSpec.configuration.acpt_test_data[:platform].run_shell(host, cmd, opts)
|
154
|
+
end
|
155
|
+
|
156
|
+
# This method runs puppet apply on the test node using the provided manifest.
|
157
|
+
def apply_manifest(manifest, opts = {})
|
158
|
+
host = RSpec.configuration.acpt_test_data[:node_name]
|
159
|
+
opts = {
|
160
|
+
apply: puppet_apply_options(opts),
|
161
|
+
ssh_opts: RSpec.configuration.acpt_test_data[:node_data][:ssh_opts],
|
162
|
+
puppet_path: RSpec.configuration.acpt_test_data[:puppet_path],
|
163
|
+
}
|
164
|
+
result = RSpec.configuration.acpt_test_data[:platform].apply_manifest(host, manifest, opts)
|
165
|
+
handle_puppet_apply_output(result, opts[:apply])
|
166
|
+
end
|
167
|
+
|
168
|
+
# This method runs puppet apply on the test node using the provided manifest twice and
|
169
|
+
# asserts that the second run has no changes.
|
170
|
+
def idempotent_apply(manifest, opts = {})
|
171
|
+
opts.reject! { |k, _| %i[catch_changes expect_changes catch_failures expect_failures].include?(k) }
|
172
|
+
begin
|
173
|
+
apply_manifest(manifest, opts.merge({ catch_failures: true }))
|
174
|
+
rescue StandardError => e
|
175
|
+
raise "Idempotent apply failed during first apply: #{e.message}\n#{e.backtrace.join("\n")}"
|
176
|
+
end
|
177
|
+
begin
|
178
|
+
apply_manifest(manifest, opts.merge({ catch_changes: true }))
|
179
|
+
rescue StandardError => e
|
180
|
+
raise "Idempotent apply failed during second apply: #{e.message}\n#{e.backtrace.join("\n")}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'core_extensions'
|
4
|
+
require_relative 'logging'
|
5
|
+
|
6
|
+
module CemAcpt
|
7
|
+
# This module provides a method and class for extracting and formatting
|
8
|
+
# test data.
|
9
|
+
module TestData
|
10
|
+
# Returns a hash of test data.
|
11
|
+
# @param config [CemAcpt::Config] the config object
|
12
|
+
# @return [Array<Hash>] an array of test data hashes
|
13
|
+
def self.acceptance_test_data(config)
|
14
|
+
Fetcher.new(config).acceptance_test_data
|
15
|
+
end
|
16
|
+
|
17
|
+
# Fetcher provides the methods for extracting and formatting test data.
|
18
|
+
class Fetcher
|
19
|
+
include CemAcpt::Logging
|
20
|
+
using CemAcpt::CoreExtensions::ExtendedHash
|
21
|
+
|
22
|
+
attr_reader :module_dir, :acceptance_tests
|
23
|
+
|
24
|
+
# Initializes a new Fetcher object.
|
25
|
+
# @param config [CemAcpt::Config] the config object
|
26
|
+
def initialize(config)
|
27
|
+
@config = config
|
28
|
+
@module_dir = config.get('module_dir')
|
29
|
+
@acceptance_tests = find_acceptance_tests(config.get('module_dir'))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Extracts, formats, and returns a test data hash.
|
33
|
+
# @return [Array<Hash>] an array of test data hashes
|
34
|
+
def acceptance_test_data
|
35
|
+
logger.info 'Gathering acceptance test data...'
|
36
|
+
acceptance_tests.each_with_object([]) do |t, a|
|
37
|
+
logger.debug("Processing #{t}...")
|
38
|
+
test_name = File.basename(t, '_spec.rb')
|
39
|
+
test_data = {
|
40
|
+
test_name: test_name,
|
41
|
+
test_file: File.expand_path(t),
|
42
|
+
}
|
43
|
+
next unless @config.has?('tests') && @config.get('tests').include?(test_name)
|
44
|
+
|
45
|
+
process_for_each(test_data).each do |test_data_i|
|
46
|
+
process_static_vars(test_data_i)
|
47
|
+
process_name_pattern_vars(test_name, test_data_i)
|
48
|
+
vars_post_processing!(test_data_i)
|
49
|
+
test_data_i.format!
|
50
|
+
a << test_data_i
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Locates acceptance tests in the module directory.
|
58
|
+
# @return [Array<String>] the list of acceptance test paths
|
59
|
+
def find_acceptance_tests(module_dir)
|
60
|
+
tests = Dir.glob(File.join(module_dir, 'spec', 'acceptance', '*_spec.rb'))
|
61
|
+
raise 'No acceptance tests found' if tests.empty?
|
62
|
+
|
63
|
+
logger.info "Found #{tests.size} acceptance tests"
|
64
|
+
tests
|
65
|
+
end
|
66
|
+
|
67
|
+
# Processes a for_each statement in the test data config.
|
68
|
+
# @param test_data [Hash] the test data hash
|
69
|
+
# @return [Array<Hash>] the list of test data hashes
|
70
|
+
def process_for_each(test_data)
|
71
|
+
return [test_data] unless @config.has?('test_data.for_each')
|
72
|
+
|
73
|
+
@config.get('test_data.for_each').each_with_object([]) do |(k, v), a|
|
74
|
+
v.each do |v_i|
|
75
|
+
test_data_i = test_data.dup
|
76
|
+
test_data_i[k] = v_i
|
77
|
+
a << test_data_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_static_vars(test_data)
|
83
|
+
return unless @config.has?('test_data.vars')
|
84
|
+
|
85
|
+
vars = @config.get('test_data.vars').each_with_object({}) do |(k, v), h|
|
86
|
+
logger.debug("Static variable: #{k}")
|
87
|
+
h[k] = v
|
88
|
+
end
|
89
|
+
test_data.merge!(vars)
|
90
|
+
test_data.format!
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_name_pattern_vars(test_name, test_data)
|
94
|
+
name_pattern_vars = name_pattern_matches(test_name) || {}
|
95
|
+
test_data.merge!(name_pattern_vars)
|
96
|
+
test_data.format!
|
97
|
+
end
|
98
|
+
|
99
|
+
def name_pattern_matches(test_name)
|
100
|
+
return test_name unless @config.has?('test_data.name_pattern_vars')
|
101
|
+
|
102
|
+
pattern = @config.get('test_data.name_pattern_vars')
|
103
|
+
pattern_matches = test_name.match(pattern)&.named_captures
|
104
|
+
unless pattern_matches
|
105
|
+
logger.error("Test name #{test_name} does not match pattern #{pattern.inspect}")
|
106
|
+
return
|
107
|
+
end
|
108
|
+
pattern_matches
|
109
|
+
end
|
110
|
+
|
111
|
+
def vars_post_processing!(test_data)
|
112
|
+
return unless @config.has?('test_data.vars_post_processing')
|
113
|
+
|
114
|
+
post_processing_new_vars!(test_data)
|
115
|
+
post_processing_delete_vars!(test_data)
|
116
|
+
end
|
117
|
+
|
118
|
+
def post_processing_new_vars!(test_data)
|
119
|
+
new_vars = @config.get('test_data.vars_post_processing.new_vars')
|
120
|
+
return unless new_vars
|
121
|
+
|
122
|
+
new_vars.each do |var|
|
123
|
+
if var.has?('string_split')
|
124
|
+
test_data[var.dot_dig('name')] = op_string_split(test_data, var)
|
125
|
+
else
|
126
|
+
logger.error("Unknown post processing operation for new var #{var}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def op_string_split(test_data, var)
|
132
|
+
from_var = var.dot_dig('string_split.from')
|
133
|
+
from = test_data.dot_dig(from_var)
|
134
|
+
parts = from.split(var.dot_dig('string_split.using'))
|
135
|
+
var.has?('string_split.part') ? parts[var.dot_dig('string_split.part')] : parts
|
136
|
+
end
|
137
|
+
|
138
|
+
def post_processing_delete_vars!(test_data)
|
139
|
+
delete_vars = @config.get('test_data.vars_post_processing.delete_vars')
|
140
|
+
return unless delete_vars
|
141
|
+
|
142
|
+
test_data.reject! { |k, _| delete_vars.include?(k.to_s) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent-ruby'
|
4
|
+
require 'open3'
|
5
|
+
require_relative '../context'
|
6
|
+
require_relative '../logging'
|
7
|
+
require_relative '../puppet_helpers'
|
8
|
+
require_relative 'runner'
|
9
|
+
|
10
|
+
module CemAcpt
|
11
|
+
module TestRunner
|
12
|
+
# Raised when the RunHandler needs to be operating within the module directory and isn't
|
13
|
+
class RunHandlerNotInModuleDirError < StandardError; end
|
14
|
+
|
15
|
+
# Raised when the RunHandler executes a system command that fails
|
16
|
+
class RunHandlerSystemCommandError < StandardError; end
|
17
|
+
|
18
|
+
# RunHandler orchestrates the acceptance test suites, including
|
19
|
+
# creating Runner objects, handling input and output, and exception
|
20
|
+
# handling.
|
21
|
+
class RunHandler
|
22
|
+
include CemAcpt::Logging
|
23
|
+
|
24
|
+
attr_accessor :run_context
|
25
|
+
|
26
|
+
# @param run_context [CemAcpt::Context::Ctx] The context object for this run
|
27
|
+
def initialize(run_context)
|
28
|
+
@run_context = run_context
|
29
|
+
@module_pkg_path = Concurrent::IVar.new
|
30
|
+
@runners = Concurrent::Array.new
|
31
|
+
@thread_pool = Concurrent::SimpleExecutorService.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets the overall exit code for all runners
|
35
|
+
def exit_code
|
36
|
+
@runners&.map { |r| r.run_result.exit_status }&.all?(&:zero?) ? 0 : 1
|
37
|
+
end
|
38
|
+
|
39
|
+
# Runs the acceptance test suites.
|
40
|
+
def run
|
41
|
+
logger.info('RUN HANDLER: Creating and starting test runners...')
|
42
|
+
create_runners
|
43
|
+
logger.info("RUN HANDLER: Created #{@runners.length} runners...")
|
44
|
+
@runners.map { |r| @thread_pool.post { r.start } }
|
45
|
+
@thread_pool.shutdown
|
46
|
+
@thread_pool.wait_for_termination
|
47
|
+
@thread_pool = Concurrent::SimpleExecutorService.new
|
48
|
+
logger.info('Test runners have all exited...')
|
49
|
+
rescue StandardError => e
|
50
|
+
handle_fatal_error(e)
|
51
|
+
ensure
|
52
|
+
handle_test_results
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroy_test_nodes
|
56
|
+
return if @runners.empty? || @runners.none?(&:node_exists)
|
57
|
+
|
58
|
+
@runners.each do |r|
|
59
|
+
r.send(:destroy) if r.node_exists
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Creates and starts Runner objects for each node in the acceptance test suites.
|
66
|
+
def create_runners
|
67
|
+
@run_context.nodes.each do |platform, nodes|
|
68
|
+
@runners += nodes.map { |node| create_runner(node, platform) }.compact
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_runner(node, platform)
|
73
|
+
logger.info("RUN HANDLER: Creating runner for #{node.test_data[:test_name]} on node #{node.node_name}...")
|
74
|
+
runner = CemAcpt::TestRunner::Runner.new(node, @run_context, platform)
|
75
|
+
runner || runner_creation_error(node)
|
76
|
+
end
|
77
|
+
|
78
|
+
def runner_creation_error(node)
|
79
|
+
msg = [
|
80
|
+
"Failed to create runner object for node #{node.node_name}.",
|
81
|
+
"Cannot run test #{node.test_data[:test_name]}",
|
82
|
+
].join(' ')
|
83
|
+
logger.error(msg)
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Handles how test results are logged.
|
88
|
+
def handle_test_results
|
89
|
+
@runners.each do |runner|
|
90
|
+
result = runner.run_result.to_h
|
91
|
+
logger << "::group::{Results for #{runner.node.test_data[:test_name]}}\n"
|
92
|
+
if result.key?('summary_line')
|
93
|
+
logger.info("SUMMARY: #{result['summary_line']} for test #{runner.node.test_data[:test_name]}")
|
94
|
+
else
|
95
|
+
handle_runner_error_results(runner)
|
96
|
+
logger << "::endgroup::\n"
|
97
|
+
next
|
98
|
+
end
|
99
|
+
unless runner.test_failures?
|
100
|
+
logger << "::endgroup::\n"
|
101
|
+
next
|
102
|
+
end
|
103
|
+
|
104
|
+
if result.key?('examples') # Log errors outside of examples
|
105
|
+
if result['examples'].empty? && !result['messages'].empty?
|
106
|
+
logger.error(result['messages'].join("\n"))
|
107
|
+
else
|
108
|
+
failed = result['examples'].reject { |e| e['status'] == 'passed' }
|
109
|
+
failed.each do |e|
|
110
|
+
logger.error(test_error_msg(runner.node.node_name, e))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
handle_runner_error_results(runner)
|
115
|
+
end
|
116
|
+
#debug_test_results(runner) if logger.debug?
|
117
|
+
logger << "::endgroup::\n"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Handles logging the results of the runners that errored.
|
122
|
+
def handle_runner_error_results(runner)
|
123
|
+
logger.error("SUMMARY: Encountered an error with test #{runner.node.test_data[:test_name]} on node #{runner.node.node_name}")
|
124
|
+
runner.run_result.to_h.each do |k, v|
|
125
|
+
logger.error("#{k.upcase}: #{v}")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Formats the error message for a runner that failed.
|
130
|
+
# @param node [Object] the node that failed
|
131
|
+
# @param err [Object] the error that occured
|
132
|
+
# @return [String] the formatted error message
|
133
|
+
def runner_error_msg(node, err)
|
134
|
+
[
|
135
|
+
"Error while running acceptance tests on node #{node.node_name}",
|
136
|
+
"Error: #{err.message}",
|
137
|
+
'Backtrace:',
|
138
|
+
err.backtrace.join("\n"),
|
139
|
+
].join("\n")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Formats a test result for tests that have failed. Is used for logging.
|
143
|
+
# @param node [String] the name of the node the test ran on
|
144
|
+
# @param result [Hash] the test result to format
|
145
|
+
# @return [String] the formatted test result
|
146
|
+
def test_error_msg(node, result)
|
147
|
+
[
|
148
|
+
"TEST FAILED: #{result['id']}",
|
149
|
+
"DESCRIPTION: #{result['full_description']}",
|
150
|
+
"STATUS: #{result['status']}",
|
151
|
+
"LOCATION: #{result['file_path']}:#{result['line_number']}",
|
152
|
+
"NODE: #{node}",
|
153
|
+
result['exception']['message'],
|
154
|
+
"\n",
|
155
|
+
].join("\n")
|
156
|
+
end
|
157
|
+
|
158
|
+
# Logs performance data for the acceptance test suites.
|
159
|
+
# This is only logged if the log level is set to debug.
|
160
|
+
# def debug_test_results(runner)
|
161
|
+
# examples_by_time = []
|
162
|
+
# runner.run_result.to_h.each do |node, result|
|
163
|
+
# next unless result['examples']
|
164
|
+
|
165
|
+
# result['examples'].each do |e|
|
166
|
+
# examples_by_time << [e['run_time'], e['id'], e['status'], e['line_number'], node]
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
# if examples_by_time
|
170
|
+
# logger.debug('Showing test results in order of execution time...')
|
171
|
+
# examples_by_time.sort_by(&:first).reverse.each do |e|
|
172
|
+
# logger.debug("RUNTIME: #{e[0]}; ID: #{e[1]}; STATUS: #{e[2]}; LINE: #{e[3]}; HOST: #{e[4]};")
|
173
|
+
# end
|
174
|
+
# else
|
175
|
+
# logger.debug('No debug results to show')
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
|
179
|
+
# Gracefully handles a fatal error and exits the program.
|
180
|
+
# @param err [StandardError, Exception] the error that caused the fatal error
|
181
|
+
def handle_fatal_error(err)
|
182
|
+
logger.fatal("RUN HANDLER: Fatal error: #{err.message}")
|
183
|
+
logger.debug(err.backtrace.join("\n"))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|