Module: CemWinSpec

Extended by:
Logging
Defined in:
lib/cem_win_spec.rb,
lib/cem_win_spec/logging.rb,
lib/cem_win_spec/version.rb,
lib/cem_win_spec/win_exec.rb,
lib/cem_win_spec/iap_tunnel.rb,
lib/cem_win_spec/test_runner.rb,
lib/cem_win_spec/fixture_cache.rb,
lib/cem_win_spec/win_exec/exec.rb,
lib/cem_win_spec/rspec_test_cmds.rb,
lib/cem_win_spec/win_exec/output.rb,
lib/cem_win_spec/win_exec/factory.rb,
lib/cem_win_spec/logging/formatter.rb,
lib/cem_win_spec/win_exec/cmd/base_cmd.rb,
lib/cem_win_spec/module_archive_builder.rb,
lib/cem_win_spec/win_exec/cmd/local_cmd.rb,
lib/cem_win_spec/win_exec/cmd/winrm_cmd.rb,
lib/cem_win_spec/win_exec/connection_opts.rb,
sig/cem_win_spec.rbs

Defined Under Namespace

Modules: Logging, WinExec Classes: CemWinSpecValidationError, FixtureCache, FixtureCacheError, IapTunnel, IapTunnelStartError, ModuleArchiveBuilder, RspecTestCmds, TestRunner

Constant Summary collapse

VERSION =

Returns:

  • (String)
"0.1.10"

Class Method Summary collapse

Class Method Details

.check_output!(output, runner = nil) ⇒ Object



184
185
186
187
188
189
# File 'lib/cem_win_spec.rb', line 184

def self.check_output!(output, runner = nil)
  unless output.success?
    runner&.iap_tunnel&.stop(wait: false, log: false)
    raise "Command failed with exit code #{output.exitcode}"
  end
end

.clean_fixture_cache(runner, module_dir, **opts) ⇒ Object

Clean the remote fixture cache

Parameters:

  • options (Hash)

    Options for the test runner



178
179
180
181
182
# File 'lib/cem_win_spec.rb', line 178

def self.clean_fixture_cache(runner, module_dir, **opts)
  validate_output do
    runner.clean_fixture_cache(working_dir: module_dir, **opts).run
  end
end

.clean_up(runner, working_dir, **opts) ⇒ Object

Clean up the remote working directory

Parameters:

  • options (Hash)

    Options for the test runner



167
168
169
170
171
172
173
174
# File 'lib/cem_win_spec.rb', line 167

def self.clean_up(runner, working_dir, **opts)
  validate_output do
    runner.clean_up(operation_timeout: 300,
                    receive_timeout: 310,
                    working_dir: working_dir,
                    **opts).run
  end
end

.exit_codeObject



11
12
13
# File 'lib/cem_win_spec.rb', line 11

def self.exit_code
  @exit_code ||= 0
end

.exit_code=(value) ⇒ Object



15
16
17
# File 'lib/cem_win_spec.rb', line 15

def self.exit_code=(value)
  @exit_code = value
end

.handle_run_error(err) ⇒ Object



97
98
99
100
101
# File 'lib/cem_win_spec.rb', line 97

def self.handle_run_error(err)
  logger.fatal "Error: #{err.message}"
  logger.debug err.backtrace.join("\n")
  self.exit_code = 1
end

.retry_spec_on_error(runner, module_dir, **options) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/cem_win_spec.rb', line 103

def self.retry_spec_on_error(runner, module_dir, **options)
  logger.info 'Cleaning cache and trying again...'
  validate_output do
    clean_fixture_cache(runner, module_dir, **options)
  end
  validate_output do
    run_spec(runner, module_dir, **options)
  end
end

.run(operation = :spec, options = {}) ⇒ Object

Run cem_win_spec operations

Parameters:

  • options (Hash) (defaults to: {})

    Options for the test runner

Options Hash (options):

  • :quiet (Boolean) — default: false

    Whether to suppress output

  • :debug (Boolean) — default: false

    Whether to enable debug output

  • :log_level (String) — default: nil

    Explicitly set the log level

  • :log_file (String) — default: nil

    Log output to file

  • :log_format (String) — default: nil

    Set log format(text, json, github_action)



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/cem_win_spec.rb', line 39

def self.run(operation = :spec, options = {})
  raise 'Must be ran from the root of the project' unless File.exist?('Gemfile')

  log_setup!(options)
  logger.info 'Running cem-win-spec...'
  logger.debug "Options: #{options}"
  runner = TestRunner.new
  logger.debug "Created TestRunner: #{runner}"
  signal_handler(runner)
  logger.debug 'Set up signal handler'
  working_dir = nil
  begin
    sre_out = setup_remote_environment(runner)
    working_dir = sre_out.stdout.chomp
    logger.debug "Working dir: #{working_dir}"
    upload_out = upload_module(runner, working_dir)
    module_dir = upload_out.stdout.chomp
    logger.debug "Module dir: #{module_dir}"
    setup_remote_ruby(runner, module_dir, **options)
    case operation
    when :spec
      run_spec(runner, module_dir, **options)
      # We set this to 0 if the tests pass because this can be set to 1 earlier by commands that fail and retry.
      self.exit_code = 0
    when :clean_fixture_cache
      clean_fixture_cache(runner, module_dir, **options)
      # We set this to 0 if the tests pass because this can be set to 1 earlier by commands that fail and retry.
      self.exit_code = 0
    else
      raise ArgumentError, "Unknown operation: #{operation}"
    end
  rescue StandardError => e
    if operation == :spec && runner.retry?
      begin
        logger.warn "Error running spec: #{e.message}"
        logger.debug e.backtrace.join("\n")
        retry_spec_on_error(runner, module_dir, **options)
      rescue StandardError => err
        handle_run_error(err)
      end
    else
      handle_run_error(e)
    end
  ensure
    if working_dir
      begin
        logger.info "Cleaning up working directory #{working_dir}"
        clean_up(runner, working_dir, **options)
        logger.debug 'Finished cleaning up working directory'
      rescue StandardError => e
        handle_run_error(e)
      end
    end
    runner&.iap_tunnel&.stop(log: false)
    logger.info 'Finished running cem-win-spec'
  end
end

.run_spec(runner, module_dir, **opts) ⇒ Object

Runs RSpec tests



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cem_win_spec.rb', line 142

def self.run_spec(runner, module_dir, **opts)
  output = validate_output(validation_method: :inspect) do
    runner.rspec_tests_parallel(operation_timeout: 300,
                                receive_timeout: 310,
                                working_dir: module_dir,
                                ignore_exitcode: true,
                                reuse_tunnel: false,
                                **opts).run
  end
  # `output.stderr.include?('Tests Failed')` only catches the specific string
  # parallel_tests prints when some rspec examples fail. It misses any other
  # reason `bundle exec rake cem_win_spec:parallel_spec` can exit non-zero on
  # the remote host - e.g. a `rake aborted!` crash during `cem_win_spec:prep`
  # (fixture cache setup, Hiera data copy, etc.) before rspec ever runs, which
  # never prints "Tests Failed" at all. `ignore_exitcode: true` on the .run
  # call above only suppresses Exec#run's own internal raise; output.exitcode
  # still holds the real remote exit code, so check it too.
  if !output.success? || output.stderr&.include?('Tests Failed')
    logger.error 'Tests failed'
    self.exit_code = 1
  end
end

.setup_remote_environment(runner) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/cem_win_spec.rb', line 113

def self.setup_remote_environment(runner)
  validate_output do
    runner.enable_long_paths.run
  end
  validate_output do
    runner.enable_symlinks.run
  end
  validate_output do
    runner.create_working_dir.run
  end
end

.setup_remote_ruby(runner, module_dir, **opts) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/cem_win_spec.rb', line 131

def self.setup_remote_ruby(runner, module_dir, **opts)
  validate_output do
    runner.setup_ruby(operation_timeout: 300,
                      receive_timeout: 310,
                      working_dir: module_dir,
                      reuse_tunnel: false,
                      **opts).run
  end
end

.signal_handler(runner) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/cem_win_spec.rb', line 19

def self.signal_handler(runner)
  Signal.trap('INT') do
    puts 'Caught SIGINT, killing tunnel and exiting'
    runner&.iap_tunnel&.stop(wait: false, log: false)
    exit 1
  end
  Signal.trap('TERM') do
    puts 'Caught SIGTERM, killing tunnel and exiting'
    runner&.iap_tunnel&.stop(wait: false, log: false)
    exit!
  end
end

.upload_module(runner, working_dir) ⇒ Object



125
126
127
128
129
# File 'lib/cem_win_spec.rb', line 125

def self.upload_module(runner, working_dir)
  validate_output do
    runner.upload_module(operation_timeout: 300, receive_timeout: 310, working_dir: working_dir).run
  end
end

.validate_output(*args, validation_method: :success?, no_output_strat: :debug, **kwargs) ⇒ Object

Raises:

  • (ArgumentError)


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/cem_win_spec.rb', line 193

def self.validate_output(*args, validation_method: :success?, no_output_strat: :debug, **kwargs)
  raise ArgumentError, 'Block required' unless block_given?

  output = yield *args, **kwargs
  unless output
    case no_output_strat
    when :raise
      raise 'No output'
    when :warn
      logger.warn 'No output'
    when :debug
      logger.debug 'No output'
    else
      # Do nothing
    end
    return false
  end

  unless output.send(validation_method)
    raise CemWinSpecValidationError, "Output validation \"#{validation_method}\" failed:\n#{output}"
  end
  output
end