cem_acpt 0.3.3-universal-java-17 → 0.3.4-universal-java-17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50d9f549fee6436b9f857dbdf313dcab94b223e988c2cbfeadd8999d86f436f0
4
- data.tar.gz: 62a556754fe568b9d3bbc28b7381b3657c7aa0db872e5405bb392a1b9cef061d
3
+ metadata.gz: c289fcb52a1115630ffece04c465e5a8d945bdefdcc99fbf73a800398d2f2bc6
4
+ data.tar.gz: aca0f41efe831bf5bb8dc4399cd1a2d771f7bb72fb1ee13eeccb79d59bd45183
5
5
  SHA512:
6
- metadata.gz: bf027699bcf399a68a6b845ef4191b4b912736f6eb52708e35a0393e78fce74f3c0299ee627bbfe80ec06a8ef9be0e5b030db6a1ab99a678943c8ebdf21112f6
7
- data.tar.gz: eb7448394e6be3719a735cf30adedae0aefb0ea8a6144523300c2e57da61bdbb3dfbf76a394b3c2014cc78a8283a6c565f2f311481951d09f1211454f22add74
6
+ metadata.gz: 65dd7b8c5023304f0c5f9fd371a64cfb157567004d6f93ebdadc76bbf99f50f7483fab575f73fb9c07256cbb0db688ad4d8c10a6b644a8f7670a3441a54e866e
7
+ data.tar.gz: 2504bc3dc1ef1ac1a09c16d00c3cd99b08127ccd31f2b9da134f31ffb2dc19730eda580ec893def0a9b183bd4198c5aa8263ea735f50c0b2c21156a5f8f7124d
@@ -0,0 +1,36 @@
1
+ name: Unit Tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ types:
7
+ - opened
8
+ - synchronize
9
+ branches:
10
+ - main
11
+ tags:
12
+ - v.*
13
+
14
+ jobs:
15
+ tests:
16
+ name: RSpec tests
17
+ runs-on: ubuntu-20.04
18
+ steps:
19
+ - name: Checkout Source
20
+ uses: actions/checkout@v3
21
+
22
+ - name: Set up Java 17
23
+ uses: actions/setup-java@v3
24
+ with:
25
+ distribution: 'adopt-hotspot'
26
+ java-version: '17'
27
+
28
+ - name: Set up JRuby
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: jruby-9.3.3.0
32
+ bundler-cache: true
33
+
34
+ - name: Run RSpec
35
+ run: |
36
+ bundle exec rake spec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_acpt (0.3.3-universal-java-17)
4
+ cem_acpt (0.3.4-universal-java-17)
5
5
  concurrent-ruby (>= 1.1, < 2.0)
6
6
  deep_merge (>= 1.2, < 2.0)
7
7
  ed25519 (>= 1.2, < 2.0)
@@ -13,10 +13,13 @@ GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
15
  ast (2.4.2)
16
+ coderay (1.1.3)
16
17
  concurrent-ruby (1.1.10)
17
18
  deep_merge (1.2.2)
18
19
  diff-lcs (1.5.0)
19
20
  ed25519 (1.3.0-java)
21
+ ffi (1.15.5-java)
22
+ method_source (1.0.0)
20
23
  minitar (0.9)
21
24
  multi_json (1.15.0)
22
25
  net-scp (4.0.0)
@@ -27,6 +30,10 @@ GEM
27
30
  parser (3.1.2.0)
28
31
  ast (~> 2.4.1)
29
32
  pathspec (1.0.0)
33
+ pry (0.14.2-java)
34
+ coderay (~> 1.1)
35
+ method_source (~> 1.0)
36
+ spoon (~> 0.0)
30
37
  puppet-modulebuilder (0.3.0)
31
38
  minitar (~> 0.9)
32
39
  pathspec (>= 0.2.1, < 2.0.0)
@@ -73,6 +80,8 @@ GEM
73
80
  net-ssh (>= 2.7)
74
81
  net-telnet (= 0.1.1)
75
82
  sfl
83
+ spoon (0.0.6)
84
+ ffi
76
85
  unicode-display_width (2.2.0)
77
86
 
78
87
  PLATFORMS
@@ -80,9 +89,10 @@ PLATFORMS
80
89
 
81
90
  DEPENDENCIES
82
91
  cem_acpt!
92
+ pry
83
93
  rake (>= 12.0)
84
94
  rspec (>= 3.0)
85
95
  rubocop
86
96
 
87
97
  BUNDLED WITH
88
- 2.3.25
98
+ 2.3.15
data/cem_acpt.gemspec CHANGED
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'puppet-modulebuilder', '>= 0.0.1'
35
35
  spec.add_runtime_dependency 'serverspec-cem-acpt'
36
36
  spec.add_development_dependency 'rubocop'
37
+ spec.add_development_dependency 'pry'
37
38
  end
@@ -4,6 +4,7 @@ module CemAcpt::Platform::Gcp
4
4
  require 'json'
5
5
  require 'open3'
6
6
  require_relative File.join(__dir__, '..', 'base', 'cmd.rb')
7
+ require_relative File.join(__dir__, '..', 'utils', 'linux.rb')
7
8
 
8
9
  # This class provides methods to run gcloud commands. It allows for default values to be
9
10
  # set for the project, zone, and user and can also find these values from the local config.
@@ -59,6 +60,10 @@ module CemAcpt::Platform::Gcp
59
60
  @local_port ||= rand(49_512..65_535)
60
61
  end
61
62
 
63
+ def os_release
64
+ @os_release || :not_set
65
+ end
66
+
62
67
  def ssh_key
63
68
  return @ssh_key unless @ssh_key.nil?
64
69
 
@@ -173,12 +178,38 @@ module CemAcpt::Platform::Gcp
173
178
  logger.debug('Restarting SSH service')
174
179
  gcloud_ssh(instance_name, 'sudo systemctl restart sshd', ignore_command_errors: true)
175
180
  logger.info("SSH connection to #{instance_name} is ready")
181
+ logger.debug('Getting OS release')
182
+ osr = gcloud_ssh(instance_name, 'sudo cat /etc/os-release')
183
+ @os_release = CemAcpt::Platform::Utils::Linux::OSRelease.new(osr)
184
+ logger.debug("OS release: #{@os_release.to_h}")
176
185
  true
177
186
  rescue StandardError => e
178
187
  logger.debug("SSH connection to #{instance_name} failed: #{e}")
179
188
  false
180
189
  end
181
190
 
191
+ def dnf_automatic_success?(instance_name, opts: {})
192
+ ssh_options = ssh_opts(instance_name: instance_name, opts: opts)
193
+ logger.debug("Checking dnf-automatic success on #{instance_name} with options #{ssh_options}")
194
+ gcloud_ssh(instance_name, 'sudo systemctl restart dnf-automatic.timer')
195
+ true
196
+ rescue StandardError => e
197
+ logger.error("DNF automatic updates on #{instance_name} failed: #{e}")
198
+ false
199
+ end
200
+
201
+ def rpm_db_check_success?(instance_name, pkgmgr, opts: {})
202
+ ssh_options = ssh_opts(instance_name: instance_name, opts: opts)
203
+ logger.debug("Checking #{pkgmgr} rpm db on #{instance_name} with options #{ssh_options}")
204
+ gcloud_ssh(instance_name, "sudo #{pkgmgr} upgrade -y rpm glibc")
205
+ gcloud_ssh(instance_name, "sudo rm -f /var/lib/rpm/.rpm.lock")
206
+ gcloud_ssh(instance_name, "sudo #{pkgmgr} upgrade -y #{pkgmgr}")
207
+ true
208
+ rescue StandardError => e
209
+ logger.error("#{pkgmgr} rpm db check on #{instance_name} failed: #{e}")
210
+ false
211
+ end
212
+
182
213
  # This function spawns a background thread to run a GCP IAP tunnel, run the given
183
214
  # code block, then kill the thread. The code block will be yielded ssh_opts that
184
215
  # are used to configure SSH connections over the IAP tunnel. The IAP tunnel is
@@ -236,6 +236,7 @@ module CemAcpt::Platform::Gcp
236
236
  node_data: data,
237
237
  transport: :ssh,
238
238
  ssh_opts: opts,
239
+ os_release: os_release.to_h,
239
240
  }
240
241
  end
241
242
 
@@ -260,6 +261,29 @@ module CemAcpt::Platform::Gcp
260
261
  false
261
262
  end
262
263
 
264
+ def dnf_automatic_success?
265
+ if os_release.name.match?(%r{^Red Hat.*}) && os_release.version.match?(%r{^(8|9).*})
266
+ logger.debug("Checking dnf-automatic on #{name} with platform #{info[:os_release][:platform]}")
267
+ @cmd.dnf_automatic_success?(name)
268
+ else
269
+ true
270
+ end
271
+ end
272
+
273
+ def rpm_db_check_success?
274
+ if os_release.name.match?(%r{^Red Hat.*})
275
+ logger.debug("Checking rpmdb on #{name} with platform #{info[:os_release][:platform]}")
276
+ pkgmgr = os_release.version.match?(%r{^(8|9).*}) ? 'dnf' : 'yum'
277
+ @cmd.rpm_db_check_success?(name, pkgmgr)
278
+ else
279
+ true
280
+ end
281
+ end
282
+
283
+ def os_release
284
+ @cmd.os_release
285
+ end
286
+
263
287
  def destroy
264
288
  @cmd.delete_instance(name)
265
289
  end
@@ -35,6 +35,18 @@ module Platform
35
35
  @instance.ready?
36
36
  end
37
37
 
38
+ # Returns true if dnf_automatic ran successfully on the GCP instance
39
+ def dnf_automatic_success?
40
+ logger.debug("Checking if dnf_automatic ran successfully on #{node_name}...")
41
+ @instance.dnf_automatic_success?
42
+ end
43
+
44
+ # Returns true if rpm_db_check ran successfully on the GCP instance
45
+ def rpm_db_check_success?
46
+ logger.debug("Checking if rpm_db_check ran successfully on #{node_name}...")
47
+ @instance.rpm_db_check_success?
48
+ end
49
+
38
50
  # Runs the test suite against the GCP instance. Must be given a block.
39
51
  # If necessary, can pass information into the block to be used in the test suite.
40
52
  def run_tests(&block)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt
4
+ module Platform
5
+ module Utils
6
+ module Linux
7
+ class OSRelease
8
+ attr_reader :properties
9
+
10
+ def initialize(os_release)
11
+ @os_release = parse_string(os_release)
12
+ @properties = []
13
+ define_properties(@os_release)
14
+ end
15
+
16
+ def to_h
17
+ @os_release
18
+ end
19
+
20
+ private
21
+
22
+ def parse_string(os_release)
23
+ os_release = os_release.split("\n")
24
+ os_release = os_release.map { |line| line.split('=', 2) }.reject { |l| l.length != 2 }
25
+ os_release.map! { |k, v| [k.downcase, v.delete_prefix('"').delete_suffix('"')] }
26
+ os_release.to_h
27
+ end
28
+
29
+ def define_properties(hsh)
30
+ hsh.each do |k, v|
31
+ @properties << k
32
+ self.class.send(:define_method, k) { v }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,6 +5,8 @@ require 'English'
5
5
  require_relative '../logging'
6
6
  require_relative '../rspec_utils'
7
7
  require_relative 'runner_result'
8
+ require_relative 'runner_workflow_builder'
9
+ #require_relative 'workflow'
8
10
 
9
11
  module CemAcpt
10
12
  module TestRunner
@@ -23,6 +25,8 @@ module CemAcpt
23
25
 
24
26
  class RunnerProvisionError < RunnerStepError; end
25
27
 
28
+
29
+
26
30
  # Runner is a class that runs a single acceptance test suite on a single node.
27
31
  # It is responsible for managing the lifecycle of the test suite and
28
32
  # reporting the results back to the main thread. Runner objects are created
@@ -32,6 +36,8 @@ module CemAcpt
32
36
 
33
37
  attr_reader :node, :node_exists, :run_result
34
38
 
39
+ MAX_PROVISION_ATTEMPTS = 3
40
+
35
41
  # @param node [String] the name of the node to run the acceptance test suite on
36
42
  # @param ctx [CemAcpt::RunnerCtx] a cem_acpt Ctx (context) object
37
43
  # @param module_pkg_path [Concurrent::IVar] the path to the module package
@@ -42,34 +48,33 @@ module CemAcpt
42
48
  @debug_mode = @context.config.debug_mode?
43
49
  @node_inventory = @context.node_inventory
44
50
  @module_pkg_path = @context.module_package_path
51
+ @provision_attempts = 0
52
+ @provision_start_time = nil
45
53
  @node_exists = false
46
54
  @run_result = CemAcpt::TestRunner::RunnerResult.new(@node, debug: @debug_mode)
47
- @completed_steps = []
48
55
  validate!
49
56
  end
50
57
 
51
- def run_step(step_sym)
52
- send(step_sym)
53
- @completed_steps << step_sym
54
- rescue StandardError => e
55
- err = CemAcpt::TestRunner::RunnerStepError.new(step_sym, e)
56
- step_error_logging(err)
57
- @run_result.from_error(err)
58
- destroy unless step_sym == :destroy
59
- end
60
-
61
58
  # Executes test suite steps
62
59
  def start
63
60
  async_info("Starting test suite for #{@node.node_name}", log_prefix('RUNNER'))
64
- run_step(:provision)
65
- run_step(:bootstrap)
66
- run_step(:run_tests)
67
- run_step(:destroy)
68
- true
61
+ @workflow = new_workflow
62
+ @workflow.run
63
+ if @workflow.success?
64
+ @run_result = @workflow.last_result
65
+ @workflow.completed_steps.each do |s|
66
+ async_info("Step '#{s.name}' completed successfully", log_prefix('RUNNER'))
67
+ end
68
+ true
69
+ else
70
+ @run_result = @workflow.last_error
71
+ step_error_logging(@workflow.last_error)
72
+ false
73
+ end
69
74
  rescue StandardError => e
70
75
  step_error_logging(e)
76
+ @run_result = CemAcpt::TestRunner::RunnerResult.new(@node, debug: @debug_mode)
71
77
  @run_result.from_error(e)
72
- destroy
73
78
  end
74
79
 
75
80
  # Checks for failures in the test results.
@@ -81,6 +86,24 @@ module CemAcpt
81
86
 
82
87
  private
83
88
 
89
+ # Builds a new workflow for the runner
90
+ # @return [CemAcpt::TestRunner::Workflow::Manager] the new workflow
91
+ def new_workflow
92
+ builder = RunnerWorkflowBuilder.new(@node, config: @context.config)
93
+ builder.add_provision
94
+ builder.add_sleep(time: 30)
95
+ builder.add_wait_for_node_ssh
96
+ builder.add_check_dnf_automatic
97
+ builder.add_check_rpm_db
98
+ builder.add_save_node_to_inventory(node_inventory: @node_inventory, platform: @platform)
99
+ builder.add_check_for_module_package_path(module_pkg_path: @module_pkg_path)
100
+ builder.add_install_module(module_pkg_path: @module_pkg_path)
101
+ builder.add_check_node_inventory_file(node_inventory: @node_inventory)
102
+ builder.add_run_tests(rspec_opts: rspec_opts, rspec_cmd: CemAcpt::RSpecUtils::Command, run_result: @run_result)
103
+ builder.add_clean_up
104
+ builder.workflow
105
+ end
106
+
84
107
  def step_error_logging(err)
85
108
  prefix = err.respond_to?(:step) ? log_prefix(err.step.capitalize) : log_prefix('RUNNER')
86
109
  fatal_msg = ["runner failed: #{err.message}"]
@@ -94,93 +117,6 @@ module CemAcpt
94
117
  "#{prefix}: #{@node.test_data[:test_name]}:"
95
118
  end
96
119
 
97
- # Provisions the node for the acceptance test suite.
98
- def provision
99
- async_info("Provisioning #{@node.node_name}...", log_prefix('PROVISION'))
100
- start_time = Time.now
101
- @node.provision
102
- @node_exists = true
103
- max_retries = 60 # equals 300 seconds because we check every five seconds
104
- until @node.ready?
105
- if max_retries <= 0
106
- async_fatal("Node #{@node.node_name} failed to provision", log_prefix('PROVISION'))
107
- raise CemAcpt::TestRunner::RunnerProvisionError, "Provisioning timed out for node #{@node.node_name}"
108
- end
109
-
110
- async_info("Waiting for #{@node.node_name} to be ready for remote connections...", log_prefix('PROVISION'))
111
- max_retries -= 1
112
- sleep(5)
113
- end
114
- async_info("Node #{@node.node_name} is ready...", log_prefix('PROVISION'))
115
- node_desc = {
116
- test_data: @node.test_data,
117
- platform: @platform,
118
- local_port: @node.local_port,
119
- }.merge(@node.node)
120
- @node_inventory.add(@node.node_name, node_desc)
121
- @node_inventory.save
122
- async_info("Node #{@node.node_name} provisioned in #{Time.now - start_time} seconds", log_prefix('PROVISION'))
123
- end
124
-
125
- # Bootstraps the node for the acceptance test suite. Currently, this
126
- # just uploads and installs the module package.
127
- def bootstrap
128
- async_info("Bootstrapping #{@node.node_name}...", log_prefix('BOOTSTRAP'))
129
- until File.exist?(@module_pkg_path)
130
- async_debug("Waiting for module package #{@module_pkg_path} to exist...", log_prefix('BOOTSTRAP'))
131
- sleep(1)
132
- end
133
- async_info("Installing module package #{@module_pkg_path}...", log_prefix('BOOTSTRAP'))
134
- @node.install_puppet_module_package(@module_pkg_path)
135
- end
136
-
137
- # Runs the acceptance test suite via rspec.
138
- def run_tests
139
- attempts = 0
140
- until File.exist?(@node_inventory.save_file_path)
141
- raise 'Node inventory file not found' if (attempts += 1) > 3
142
-
143
- sleep(1)
144
- end
145
- async_info("Running test #{@node.test_data[:test_name]} on node #{@node.node_name}...", log_prefix('RSPEC'))
146
- @node.run_tests do |cmd_env|
147
- cmd_opts = rspec_opts
148
- cmd_opts.env = cmd_opts.env.merge(cmd_env) if cmd_env
149
- # Documentation format gets logged in real time, JSON file is read after the fact
150
- begin
151
- @rspec_cmd = CemAcpt::RSpecUtils::Command.new(cmd_opts)
152
- @rspec_cmd.execute(pty: false, log_prefix: log_prefix('RSPEC'))
153
- @run_result.from_json_file(cmd_opts.format[:json])
154
- rescue Errno::EIO => e
155
- async_error("failed to run rspec: #{@node.test_data[:test_name]}: #{$ERROR_INFO}", log_prefix('RSPEC'))
156
- @run_result.from_error(e)
157
- rescue StandardError => e
158
- async_error("failed to run rspec: #{@node.test_data[:test_name]}: #{e.message}", log_prefix('RSPEC'))
159
- async_debug("Backtrace:\n#{e.backtrace}", log_prefix('RSPEC'))
160
- @run_result.from_error(e)
161
- end
162
- end
163
- async_info("Tests completed with exit code: #{@run_result.exit_status}", log_prefix('RSPEC'))
164
- end
165
-
166
- # Destroys the node for the acceptance test suite.
167
- def destroy
168
- kill_spec_pty_if_exists
169
- if @context.config.get('no_destroy_nodes')
170
- async_info("Not destroying node #{@node.node_name} because 'no_destroy_nodes' is set to true",
171
- log_prefix('DESTROY'))
172
- else
173
- async_info("Destroying #{@node.node_name}...", log_prefix('DESTROY'))
174
- @node.destroy
175
- @node_exists = false
176
- async_info("Node #{@node.node_name} destroyed successfully", log_prefix('DESTROY'))
177
- end
178
- end
179
-
180
- def kill_spec_pty_if_exists
181
- @rspec_cmd&.kill_pty
182
- end
183
-
184
120
  # Validates the runner configuration.
185
121
  def validate!
186
122
  raise 'No node provided' unless @node
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logging'
4
+ require_relative 'workflow'
5
+
6
+ module CemAcpt
7
+ module TestRunner
8
+ # RunnerWorkflowBuilder builds a workflow for a TestRunner
9
+ # @!attribute [r] workflow
10
+ # @return [CemAcpt::TestRunner::Workflow::Manager] Workflow object
11
+ class RunnerWorkflowBuilder
12
+ include CemAcpt::LoggingAsync
13
+
14
+ attr_reader :workflow
15
+
16
+ # @param node [CemAcpt::Platform::Base] Initialized node object
17
+ # @param config [Hash] Context config hash
18
+ def initialize(node, config)
19
+ @node = node
20
+ @workflow = CemAcpt::TestRunner::Workflow::Manager.new(workflow_manager_opts(config))
21
+ @config = config
22
+ end
23
+
24
+ def add_sleep(**kwargs)
25
+ opts = {
26
+ node: @node,
27
+ time: kwargs[:time] || 10,
28
+ retryable: false,
29
+ }
30
+ @workflow.add_step(:sleep, **opts) do |s|
31
+ log_info("sleeping for #{s.opts[:time]} seconds", s)
32
+ sleep(s.opts[:time])
33
+ end
34
+ end
35
+
36
+ def add_provision(**kwargs)
37
+ opts = {
38
+ node: @node,
39
+ retryable: kwargs[:retryable] || false,
40
+ }
41
+ @workflow.add_step(:provision, **opts) do |s|
42
+ s.opts[:node].provision
43
+ s.opts[:node]
44
+ end
45
+ end
46
+
47
+ def add_wait_for_node_ssh(**kwargs)
48
+ opts = {
49
+ node: @node,
50
+ retryable: kwargs[:retryable] || true,
51
+ retry_delay: kwargs[:retry_delay] || 30,
52
+ retry_max: kwargs[:retry_max] || 10,
53
+ }
54
+ @workflow.add_step(:wait_for_node_ssh, **opts) do |s|
55
+ unless s.opts[:node].ready?
56
+ raise "wait_for_node_ssh timed out for node #{s.opts[:node].node_name}"
57
+ end
58
+ s.opts[:node]
59
+ end
60
+ end
61
+
62
+ def add_check_dnf_automatic(**kwargs)
63
+ opts = {
64
+ node: @node,
65
+ retryable: kwargs[:retryable] || true,
66
+ retry_delay: kwargs[:retry_delay] || 30,
67
+ retry_workflow_on_fail: kwargs[:retry_workflow_on_fail] || true,
68
+ }
69
+ @workflow.add_step(:check_dnf_automatic, **opts) do |s|
70
+ unless s.opts[:node].dnf_automatic_success?
71
+ raise "dnf_automatic failed on node #{s.opts[:node].node_name}"
72
+ end
73
+ s.opts[:node]
74
+ end
75
+ end
76
+
77
+ def add_check_rpm_db(**kwargs)
78
+ opts = {
79
+ node: @node,
80
+ retryable: kwargs[:retryable] || true,
81
+ retry_delay: kwargs[:retry_delay] || 30,
82
+ retry_workflow_on_fail: kwargs[:retry_workflow_on_fail] || true,
83
+ }
84
+ @workflow.add_step(:rpm_db_check, **opts) do |s|
85
+ unless s.opts[:node].rpm_db_check_success?
86
+ raise "rpm_db_check failed on node #{s.opts[:node].node_name}"
87
+ end
88
+ s.opts[:node]
89
+ end
90
+ end
91
+
92
+ def add_save_node_to_inventory(node_inventory:, platform:, **kwargs)
93
+ opts = {
94
+ node: @node,
95
+ platform: platform,
96
+ node_inventory: node_inventory,
97
+ retryable: kwargs[:retryable] || false,
98
+ }
99
+ @workflow.add_step(:save_node_to_inventory, **opts) do |s|
100
+ node_desc = {
101
+ test_data: s.opts[:node].test_data,
102
+ platform: s.opts[:platform],
103
+ local_port: s.opts[:node].local_port,
104
+ }.merge(s.opts[:node].node)
105
+ s.opts[:node_inventory].add(s.opts[:node].node_name, node_desc)
106
+ s.opts[:node_inventory].save
107
+ log_info("node #{s.opts[:node_name]} saved to inventory", s)
108
+ s.opts[:node_inventory]
109
+ end
110
+ end
111
+
112
+ def add_check_for_module_package_path(module_pkg_path:, **kwargs)
113
+ opts = {
114
+ module_pkg_path: module_pkg_path,
115
+ retryable: kwargs[:retryable] || true,
116
+ retry_delay: kwargs[:retry_delay] || 30,
117
+ }
118
+ @workflow.add_step(:check_for_module_package_path, **opts) do |s|
119
+ unless File.exist?(s.opts[:module_pkg_path])
120
+ raise "module package #{s.opts[:module_pkg_path]} does not exist"
121
+ end
122
+ s.opts[:module_pkg_path]
123
+ end
124
+ end
125
+
126
+ def add_install_module(module_pkg_path:, **kwargs)
127
+ opts = {
128
+ node: @node,
129
+ module_pkg_path: module_pkg_path,
130
+ retryable: kwargs[:retryable] || true,
131
+ retry_delay: kwargs[:retry_delay] || 60,
132
+ }
133
+ @workflow.add_step(:bootstrap, **opts) do |s|
134
+ s.opts[:node].install_puppet_module_package(s.opts[:module_pkg_path])
135
+ s.opts[:node]
136
+ end
137
+ end
138
+
139
+ def add_check_node_inventory_file(node_inventory:, **kwargs)
140
+ opts = {
141
+ ni_file_path: node_inventory.save_file_path,
142
+ retryable: kwargs[:retryable] || true,
143
+ retry_max: kwargs[:retry_max] || 60,
144
+ retry_delay: kwargs[:retry_delay] || 5,
145
+ }
146
+ @workflow.add_step(:check_node_inventory_file, **opts) do |s|
147
+ unless File.exist?(s.opts[:ni_file_path])
148
+ raise "node inventory file #{s.opts[:ni_file_path]} not found"
149
+ end
150
+ s.opts[:ni_file_path]
151
+ end
152
+ end
153
+
154
+ def add_run_tests(rspec_opts:, rspec_cmd:, run_result:, **kwargs)
155
+ opts = {
156
+ node: @node,
157
+ rspec_opts: rspec_opts,
158
+ rspec_cmd: rspec_cmd,
159
+ run_result: run_result,
160
+ retryable: kwargs[:retryable] || false,
161
+ }
162
+ @workflow.add_step(:run_tests, **opts) do |s|
163
+ s.opts[:node].run_tests do |cmd_env|
164
+ cmd_opts = s.opts[:rspec_opts].dup
165
+ cmd_opts.env = cmd_opts.env.merge(cmd_env) if cmd_env
166
+ rspec_cmd = s.opts[:rspec_cmd].new(cmd_opts)
167
+ run_result = s.opts[:run_result].dup
168
+ begin
169
+ rspec_cmd.execute(pty: false, log_prefix: "RSPEC: #{@node.test_data[:test_name]}")
170
+ run_result.from_json_file(cmd_opts.format[:json])
171
+ rescue Errno::EIO => e
172
+ log_error("failed to run rspec: #{@node.test_data[:test_name]}: #{$ERROR_INFO}", s)
173
+ run_result.from_error(e)
174
+ rescue StandardError => e
175
+ log_error("failed to run rspec: #{@node.test_data[:test_name]}: #{e.message}", s)
176
+ log_debug(e.backtrace.join("\n"), s)
177
+ run_result.from_error(e)
178
+ end
179
+ end
180
+ run_result
181
+ end
182
+ end
183
+
184
+ def add_clean_up(force: false, **kwargs)
185
+ opts = {
186
+ node: @node,
187
+ config: @config[:config],
188
+ force: force,
189
+ retryable: kwargs[:retryable] || false,
190
+ }
191
+ @workflow.add_step(:clean_up, **opts) do |s|
192
+ if !force && s.opts[:config].get('no_destroy_nodes')
193
+ log_info("not destroying node #{s.opts[:node].node_name} because 'no_destroy_nodes' is set to true", s)
194
+ else
195
+ log_info("destroying node #{s.opts[:node].node_name}", s)
196
+ s.opts[:node].destroy
197
+ log_info("node #{s.opts[:node].node_name} destroyed", s)
198
+ end
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def log_msg(msg, step)
205
+ "Step '#{step.name}' on #{@node.node_name}: #{msg}"
206
+ end
207
+
208
+ def log_debug(msg, step)
209
+ async_debug(log_msg(msg, step))
210
+ end
211
+
212
+ def log_info(msg, step)
213
+ async_info(log_msg(msg, step))
214
+ end
215
+
216
+ def log_warn(msg, step)
217
+ async_warn(log_msg(msg, step))
218
+ end
219
+
220
+ def log_error(msg, step)
221
+ async_error(log_msg(msg, step))
222
+ end
223
+
224
+ def log_fatal(msg, step)
225
+ async_fatal(log_msg(msg, step))
226
+ end
227
+
228
+ def workflow_manager_opts(config)
229
+ {
230
+ retry_max: config[:workflow_retry_max] || 3,
231
+ retry_delay: config[:workflow_retry_delay] || 0,
232
+ ignore_failures: config[:workflow_ignore_failures] || false,
233
+ raise_on_fail: config[:workflow_raise_on_fail] || true,
234
+ }
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../logging'
4
+ require_relative 'step'
5
+
6
+ module CemAcpt
7
+ module TestRunner
8
+ module Workflow
9
+ # Manager is a class that manages how steps in a workflow are executed.
10
+ # @!attribute [r] completed_steps
11
+ # @return [Array<Step>] The steps that have been completed
12
+ # @!attribute [r] last_error
13
+ # @return [Exception, nil] The last error that occurred, if any
14
+ # @!attribute [r] last_result
15
+ # @return [Any] The last result that occurred, if any
16
+ # @!attribute [r] retry_max
17
+ # @return [Integer] The maximum number of workflow runs to attempt before failing. Default: 3
18
+ # @!attribute [r] retry_delay
19
+ # @return [Integer] The number of seconds to wait between workflow runs. Default: 0
20
+ # @!attribute [r] steps
21
+ # @return [Array<Step>] The steps that are part of this workflow
22
+ class Manager
23
+ include CemAcpt::LoggingAsync
24
+
25
+ attr_reader :completed_steps, :last_error, :last_result, :retry_max, :retry_delay, :steps
26
+
27
+ # @param [Hash] opts The options to create a new workflow manager with
28
+ # @option opts [Integer] :retry_max The maximum number of workflow runs to attempt before failing. Default: 3
29
+ # @option opts [Integer] :retry_delay The number of seconds to wait between workflow runs. Default: 0
30
+ # @option opts [Boolean] :ignore_failures Whether to ignore Step failures and continue to the next step. Default: false
31
+ # @option opts [Boolean] :raise_on_fail Whether to raise an exception when a Step fails. Default: true
32
+ def initialize(**opts)
33
+ @retry_max = opts[:retry_max] || 3
34
+ @retry_delay = opts[:retry_delay] || 0
35
+ @ignore_failures = opts[:ignore_failures] || false
36
+ @raise_on_fail = opts[:raise_on_fail] || true
37
+ @steps = []
38
+ @workflow_runs = 0
39
+ @last_error = nil
40
+ @last_result = nil
41
+ @completed_steps = []
42
+ end
43
+
44
+ # Add a step to the workflow. Steps can be named anything, but if the step name is :clean_up,
45
+ # it will be run at the end of the workflow, regardless of the order it was added, and it's
46
+ # output will not be saved as the last_result. Additionally, the :clean_up step will be run
47
+ # even if the workflow fails.
48
+ # @param [Symbol] name The name of the step
49
+ # @param [Hash] kwargs The keyword arguments to pass to the step
50
+ # @param [Proc] block The block to pass to the step
51
+ # @yieldparam [Step] step The step that was added. This is passed to the block.
52
+ def add_step(name, **kwargs, &block)
53
+ raise ArgumentError, 'name must be a Symbol' unless name.is_a?(Symbol)
54
+
55
+ step_name = if @steps.any? { |step| step.name == name }
56
+ "#{name}_#{@steps.length}".to_sym
57
+ else
58
+ name
59
+ end
60
+ step = Step.new(step_name, **kwargs, &block)
61
+ @steps << StepState.new(step, @steps.length, **kwargs)
62
+ end
63
+
64
+ # Run the workflow
65
+ def run
66
+ @workflow_runs += 1
67
+ @completed_steps = []
68
+ @steps.each do |step|
69
+ next if step.name == :clean_up
70
+
71
+ result = step.run
72
+ handle_result(step, result)
73
+ end
74
+ ensure
75
+ clean_up
76
+ end
77
+
78
+ # Whether the workflow has completed successfully
79
+ def success?
80
+ (@completed_steps.length == @steps.length) && @completed_steps.none? { |step| step.failed? }
81
+ end
82
+
83
+ # @return [Boolean] Whether to ignore failures and continue to the next step
84
+ def ignore_failures?
85
+ @ignore_failures
86
+ end
87
+
88
+ # @return [Boolean] Whether to raise an exception when a step fails
89
+ def raise_on_fail?
90
+ @raise_on_fail
91
+ end
92
+
93
+ private
94
+
95
+ # Handles the result of a step
96
+ # @param [Step] step The step that was run
97
+ # @param [Object] result The result of the step
98
+ # @raise [StandardError] If the step failed and the workflow is not configured to ignore failures
99
+ def handle_result(step, result)
100
+ @last_result = result unless step.name == :clean_up # Don't overwrite the last result with the clean_up step
101
+ case result
102
+ when :retry_workflow
103
+ async_warn("Step '#{step.name}' failed and requested a workflow retry")
104
+ async_debug("Step '#{step.name}' failed with error: #{step.last_error.message}")
105
+ async_debug("Step '#{step.name}' failed with error: #{step.last_error.backtrace.join("\n")}")
106
+ @last_error = step.last_error
107
+ retry_workflow
108
+ when :fail
109
+ async_warn("Step '#{step.name}' failed")
110
+ async_debug(step.last_error.message)
111
+ async_debug(step.last_error.backtrace.join("\n"))
112
+ @last_error = step.last_error
113
+ if ignore_failures?
114
+ async_warn("Ignoring failure of step '#{step.name}'")
115
+ @completed_steps << step
116
+ else
117
+ async_error("Workflow failed with error: #{@last_error.message}")
118
+ raise @last_error
119
+ end
120
+ else
121
+ async_info("Step '#{step.name}' succeeded")
122
+ async_debug("Step '#{step.name}' returned: #{result}")
123
+ @completed_steps << step
124
+ end
125
+ end
126
+
127
+ # Retries the workflow if it is retryable
128
+ # @raise [StandardError] If the workflow is not retryable or has exceeded the maximum number of retries
129
+ def retry_workflow
130
+ if @workflow_runs < @retry_max
131
+ async_info("Retrying workflow (attempt #{@workflow_runs + 1} of #{@retry_max})")
132
+ sleep @retry_delay if @retry_delay > 0
133
+ clean_up
134
+ run
135
+ else
136
+ async_fatal('Workflow is not retryable or has exceeded the maximum number of retries')
137
+ raise @last_error
138
+ end
139
+ end
140
+
141
+ # Adds a default clean_up step if one is not defined
142
+ # @return [Step] The default clean_up step
143
+ def default_clean_up
144
+ add_step(:clean_up) do
145
+ async_info('No clean_up step defined, skipping')
146
+ true
147
+ end
148
+ @steps.last
149
+ end
150
+
151
+ # Runs the clean_up step
152
+ def clean_up
153
+ cleanup_step = @steps.find { |step| step.name == :clean_up } || default_clean_up
154
+ result = cleanup_step.run
155
+ handle_result(cleanup_step, result)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../logging'
4
+
5
+ module CemAcpt
6
+ module TestRunner
7
+ module Workflow
8
+ # Error used to wrap fatal errors raised in Runner steps
9
+ # @!attribute [r] step
10
+ # @return [Step] The step that raised the error
11
+ class StepError < StandardError
12
+ attr_reader :step
13
+
14
+ def initialize(step, err)
15
+ @step = step
16
+ @original_error = err
17
+ super err
18
+ set_backtrace err.backtrace if err.respond_to?(:backtrace)
19
+ end
20
+ end
21
+
22
+ # Step is a class that defines a single step in a Workflow.
23
+ # Step objects are created by the Workflow class and are not intended to be created directly.
24
+ # @!attribute [r] name
25
+ # @return [Symbol] The name of the step
26
+ # @!attribute [r] opts
27
+ # @return [Hash] The options passed to the step
28
+ # @!attribute [r] result
29
+ # @return [Object] The result of the step
30
+ class Step
31
+ include CemAcpt::LoggingAsync
32
+
33
+ attr_reader :name, :opts, :result
34
+
35
+ # @param name [Symbol] The name of the step
36
+ # @param opts [Hash] The options passed to the step
37
+ # @param block [Proc] The block to execute when the step is run
38
+ # @yieldparam step [Step] This step object is yielded to the block
39
+ def initialize(name, **opts, &block)
40
+ @name = name
41
+ @opts = opts
42
+ @block = block
43
+ @result = :not_run
44
+ @failed = false
45
+ @log_prefix = opts[:log_prefix] || "#{@name.upcase}:"
46
+ end
47
+
48
+ # @return [Boolean] True if the step has been run and failed
49
+ def failed?
50
+ @failed
51
+ end
52
+
53
+ # Run the step. This calls and executes the block passed to the constructor.
54
+ # @param log_prefix [String] The prefix to use when logging the step name
55
+ # @return [Object] The result of the step
56
+ def run(_log_prefix = 'Running step')
57
+ async_info(log_msg('Starting step'))
58
+ @result = @block.call(self)
59
+ async_debug(log_msg('SUCCESS'))
60
+ @result
61
+ rescue StandardError => e
62
+ async_debug(log_msg("FAILED: #{e.message}"))
63
+ @result = StepError.new(@name, e)
64
+ @failed = true
65
+ @result
66
+ ensure
67
+ Thread.pass # Be kind to the scheduler
68
+ end
69
+
70
+ private
71
+
72
+ def log_msg(msg)
73
+ [@log_prefix, msg].join(' ')
74
+ end
75
+ end
76
+
77
+ # StepState is a class that holds the state of a Step.
78
+ # StepState objects are created by the Workflow class and are not intended to be created directly.
79
+ # @!attribute [r] step
80
+ # @return [Step] The step object
81
+ # @!attribute [r] position
82
+ # @return [Integer] The position of the step in the workflow
83
+ # @!attribute [r] opts
84
+ # @return [Hash] The options passed to the step
85
+ # @!attribute [r] run_count
86
+ # @return [Integer] The number of times the step has been run
87
+ # @!attribute [r] results
88
+ # @return [Array<Object>] The results of all runs of the step
89
+ # @!attribute [r] last_error
90
+ # @return [StepError] The last error raised by the step
91
+ # @!attribute [r] last_result
92
+ # @return [Object] The result of the last run of the step
93
+ class StepState
94
+ attr_reader :step, :position, :opts, :run_count, :results, :last_error, :last_result
95
+
96
+ # @param step [Step] The step object
97
+ # @param position [Integer] The position of the step in the workflow
98
+ # @param opts [Hash] The options passed to the step
99
+ def initialize(step, position, **opts)
100
+ @step = step
101
+ @position = position
102
+ @retryable = opts[:retryable] || false
103
+ @retry_max = opts[:retry_max] || 3
104
+ @retry_delay = opts[:retry_delay] || 0
105
+ @retry_workflow_on_fail = opts[:retry_workflow_on_fail] || false
106
+ @run_count = 0
107
+ @results = []
108
+ @last_error = nil
109
+ @last_result = nil
110
+ end
111
+
112
+ # Proxy any methods not defined in this class to the Step
113
+ def method_missing(method, *args, &block)
114
+ if @step.respond_to?(method)
115
+ @step.send(method, *args, &block)
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ # Proxy any methods not defined in this class to the Step
122
+ def respond_to_missing?(method, include_private = false)
123
+ @step.respond_to?(method) || super
124
+ end
125
+
126
+ # @return [Object] The result of the last run of the step
127
+ def result
128
+ @last_result || :not_run
129
+ end
130
+
131
+ # @return [Boolean] If the step is retryable
132
+ def retryable?
133
+ @retryable
134
+ end
135
+
136
+ # @return [Boolean] If the workflow should be retried if the step fails
137
+ def retry_workflow_on_fail?
138
+ @retry_workflow_on_fail
139
+ end
140
+
141
+ # @return [Boolean] True if the step has been run and failed
142
+ def failed?
143
+ @results.last.is_a?(StepError)
144
+ end
145
+
146
+ # Run the step. This wraps the Step#run method and handles updating the state of the step.
147
+ # @param log_prefix [String] The prefix to use when logging the step name
148
+ # @return [Object] The result of the step
149
+ def run(log_prefix = 'Running step')
150
+ @run_count += 1
151
+ @last_result = @step.run(log_prefix)
152
+ @results << @last_result
153
+ if @last_result.is_a?(StepError)
154
+ handle_error(@last_result)
155
+ end
156
+ @last_result
157
+ end
158
+
159
+ private
160
+
161
+ # Handle the error raised by the step
162
+ # @param result [StepError] The result of the step
163
+ def handle_error(result)
164
+ @last_error = result
165
+ if retry_workflow_on_fail?
166
+ @last_result = :retry_workflow
167
+ elsif retry?
168
+ retry_step
169
+ else
170
+ @last_result = :fail
171
+ end
172
+ end
173
+
174
+ # @return [Boolean] True if the step should be retried
175
+ def retry?
176
+ @retryable && @run_count < @retry_max
177
+ end
178
+
179
+ # Retry running the step
180
+ def retry_step
181
+ sleep @retry_delay if @retry_delay > 0
182
+ run("Retrying step (attempt #{@run_count} of #{@retry_max})")
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../logging'
4
+ require_relative 'workflow/manager'
5
+ require_relative 'workflow/step'
6
+
7
+ module CemAcpt
8
+ module TestRunner
9
+ # Namespace for workflow classes
10
+ module Workflow; end
11
+ end
12
+ end
13
+
14
+ # module CemAcpt
15
+ # module TestRunner
16
+
17
+
18
+ # # Workflow is a class that manages how steps are executed.
19
+ # class Workflow
20
+ # include CemAcpt::LoggingAsync
21
+
22
+ # attr_reader :steps, :groups, :group_retry_max, :completed_steps
23
+
24
+ # def initialize(init_steps: [], **opts)
25
+ # raise ArgumentError, 'init_steps must be an Array' unless init_steps.is_a?(Array)
26
+ # raise ArgumentError, 'init_steps must be an Array of Step objects' unless init_steps.all? { |step| step.is_a?(Step) }
27
+
28
+ # @steps = init_steps
29
+ # @groups = @steps.map(&:group).compact.uniq
30
+ # @group_retry_max = opts[:group_retry_max] || 3
31
+ # @group_tries = {}
32
+ # @last_error = nil
33
+ # @completed_steps = []
34
+ # end
35
+
36
+ # def add_step(step, order = nil)
37
+ # raise ArgumentError, 'step must be a Step object' unless step.is_a?(Step)
38
+
39
+ # unless order.nil?
40
+ # @steps.insert(order, step)
41
+ # else
42
+ # @steps << step
43
+ # end
44
+ # end
45
+
46
+ # def add_step_with(name, order = nil, **kwargs, &block)
47
+ # raise ArgumentError, 'order must be an Integer' unless order.is_a?(Integer) || order.nil?
48
+ # raise ArgumentError, 'name must be a Symbol' unless name.is_a?(Symbol)
49
+
50
+ # unless order.nil?
51
+ # @steps.insert(order, Step.new(name, **kwargs, &block))
52
+ # else
53
+ # @steps << Step.new(name, **kwargs, &block)
54
+ # end
55
+ # end
56
+
57
+ # def run
58
+ # current_run_groups = []
59
+ # @steps.each do |step|
60
+ # next if step.name == :clean_up
61
+
62
+ # group_run_increment(step, current_run_groups)
63
+ # step.run
64
+ # @completed_steps << step
65
+ # end
66
+ # @completed_steps
67
+ # rescue StandardError => e
68
+ # async_error("Workflow failed with error: #{e}")
69
+ # @last_error = e
70
+ # raise e
71
+ # ensure
72
+ # clean_up
73
+ # end
74
+
75
+ # def success?
76
+ # (@completed_steps.length == @steps.length) && @completed_steps.none? { |step| step.result.is_a?(StepError) }
77
+ # end
78
+
79
+ # private
80
+
81
+ # def group_run_increment(step, current_run_groups = [])
82
+ # if step.grouped? && !current_run_groups.include?(step.group)
83
+ # current_run_groups << step.group
84
+ # if @group_tries[step.group].nil? || @group_tries[step.group].zero?
85
+ # @group_tries[step.group] = 1
86
+ # elsif @group_tries[step.group] >= @group_retry_max
87
+ # raise StepError.new(step, @last_error)
88
+ # else
89
+ # @group_tries[step.group] += 1
90
+ # end
91
+ # end
92
+ # end
93
+
94
+ # def run_step(step)
95
+ # result = step.run
96
+ # #handle_result(step, result)
97
+ # @completed_steps << step
98
+ # result
99
+ # end
100
+
101
+ # def handle_result(step, result)
102
+ # if result.is_a?(StepError)
103
+ # handle_error(step, result)
104
+ # else
105
+ # async_info("Step '#{step.name}' completed successfully")
106
+ # end
107
+ # end
108
+
109
+ # def handle_error(step, err)
110
+ # if step.retryable? && step.runs < @retry_max
111
+ # async_info("Retrying step '#{step.name}' (attempt #{step.runs + 1} of #{@retry_max})")
112
+ # sleep step.retry_delay if step.retry_delay > 0
113
+ # run_step(step)
114
+ # else
115
+ # async_debug("Step '#{step.name}' is not retryable or has exceeded the maximum number of retries")
116
+ # raise err if step.raise_on_fail?
117
+ # end
118
+ # end
119
+
120
+ # def default_clean_up
121
+ # Step.new(:clean_up, retryable: false, raise_on_fail: true) do
122
+ # async_info('No clean_up step defined, skipping')
123
+ # end
124
+ # end
125
+
126
+ # def clean_up
127
+ # cleanup_step = @steps.find { |step| step.name == :clean_up } || default_clean_up
128
+ # unless cleanup_step.nil?
129
+ # cleanup_step.retryable = false # clean_up steps should not be retried
130
+ # cleanup_step.raise_on_fail = true # clean_up steps should always raise on failure
131
+ # run_step(cleanup_step)
132
+ # end
133
+ # end
134
+ # end
135
+
136
+ # # Step is a class that defines a single step in a Workflow.
137
+ # class Step
138
+ # include CemAcpt::LoggingAsync
139
+
140
+ # attr_reader :group, :name, :opts, :result, :retry_delay, :retry_max, :runs
141
+
142
+ # def initialize(name, **opts, &logic)
143
+ # @name = name
144
+ # @logic = logic
145
+ # @group = opts[:group] || nil
146
+ # @retryable = opts[:retryable] || false
147
+ # @retry_delay = opts[:retry_delay] || 0
148
+ # @retry_max = opts[:retry_max] || 3
149
+ # @retry_group_on_fail = opts[:retry_group_on_fail] || false
150
+ # @raise_on_fail = opts[:raise_on_fail] || true
151
+ # @runs = 0
152
+ # @opts = opts
153
+ # @result = :not_run
154
+ # end
155
+
156
+ # def grouped?
157
+ # !@group.nil?
158
+ # end
159
+
160
+ # def retryable?
161
+ # @retryable
162
+ # end
163
+
164
+ # def retry_group_on_fail?
165
+ # @retry_group_on_fail
166
+ # end
167
+
168
+ # def raise_on_fail?
169
+ # @raise_on_fail
170
+ # end
171
+
172
+ # def retryable=(val)
173
+ # raise ArgumentError, 'retryable must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
174
+
175
+ # @retryable = val
176
+ # end
177
+
178
+ # def retry_delay=(val)
179
+ # raise ArgumentError, 'retry_delay must be an Integer' unless val.is_a?(Integer)
180
+
181
+ # @retry_delay = val
182
+ # end
183
+
184
+ # def retry_group_on_fail=(val)
185
+ # raise ArgumentError, 'retry_group_on_fail must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
186
+
187
+ # @retry_group_on_fail = val
188
+ # end
189
+
190
+ # def raise_on_fail=(val)
191
+ # raise ArgumentError, 'raise_on_fail must be a Boolean' unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
192
+
193
+ # @raise_on_fail = val
194
+ # end
195
+
196
+ # def run
197
+ # begin
198
+ # @runs += 1
199
+ # async_info("Running step #{@name}...") if @runs == 1
200
+ # @result = @logic.call(@opts)
201
+ # rescue StandardError => e
202
+ # if retryable? && @runs < @retry_max
203
+ # sleep @retry_delay if @retry_delay > 0
204
+ # async_info("Retrying step #{@name} (attempt #{@runs + 1} of #{@retry_max})")
205
+ # run
206
+ # else
207
+ # @result = CemAcpt::TestRunner::StepError.new(@name, e)
208
+ # raise @result if raise_on_fail?
209
+ # end
210
+ # end
211
+ # @result
212
+ # end
213
+ # end
214
+ # end
215
+ # end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.4'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cem_acpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: universal-java-17
6
6
  authors:
7
7
  - puppetlabs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-14 00:00:00.000000000 Z
11
+ date: 2023-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,20 @@ dependencies:
132
132
  - - ">="
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
+ - !ruby/object:Gem::Dependency
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ name: pry
142
+ prerelease: false
143
+ type: :development
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
135
149
  description: Litmus-like library focusing on CEM Acceptance Tests
136
150
  email:
137
151
  - abide-team@puppet.com
@@ -140,6 +154,7 @@ executables:
140
154
  extensions: []
141
155
  extra_rdoc_files: []
142
156
  files:
157
+ - ".github/workflows/spec.yml"
143
158
  - ".gitignore"
144
159
  - ".rspec"
145
160
  - CODEOWNERS
@@ -167,6 +182,7 @@ files:
167
182
  - lib/cem_acpt/platform/gcp.rb
168
183
  - lib/cem_acpt/platform/gcp/cmd.rb
169
184
  - lib/cem_acpt/platform/gcp/compute.rb
185
+ - lib/cem_acpt/platform/utils/linux.rb
170
186
  - lib/cem_acpt/platform/vmpooler.rb
171
187
  - lib/cem_acpt/puppet_helpers.rb
172
188
  - lib/cem_acpt/rspec_utils.rb
@@ -177,6 +193,10 @@ files:
177
193
  - lib/cem_acpt/test_runner/run_handler.rb
178
194
  - lib/cem_acpt/test_runner/runner.rb
179
195
  - lib/cem_acpt/test_runner/runner_result.rb
196
+ - lib/cem_acpt/test_runner/runner_workflow_builder.rb
197
+ - lib/cem_acpt/test_runner/workflow.rb
198
+ - lib/cem_acpt/test_runner/workflow/manager.rb
199
+ - lib/cem_acpt/test_runner/workflow/step.rb
180
200
  - lib/cem_acpt/utils.rb
181
201
  - lib/cem_acpt/version.rb
182
202
  - sample_config.yaml