Class: Polytrix::Challenge

Inherits:
Dash
  • Object
show all
Includes:
Documentation::Helpers::CodeHelper, Logging, Util::FileSystem, Util::String
Defined in:
lib/polytrix/challenge.rb

Overview

rubocop:disable ClassLength

Defined Under Namespace

Classes: FSM

Constant Summary collapse

KEYS_TO_PERSIST =
[:result, :spy_data]

Instance Method Summary collapse

Methods included from Documentation::Helpers::CodeHelper

#code_block, #highlighted_code, #snippet_after, #snippet_between, #source, #source?

Methods included from Util::String

included

Methods included from Util::String::ClassMethods

#ansi2html, #escape_html, #highlight, #slugify

Methods included from Util::FileSystem

#find_file, #relativize

Constructor Details

#initialize(hash) ⇒ Challenge

Returns a new instance of Challenge.



30
31
32
33
# File 'lib/polytrix/challenge.rb', line 30

def initialize(hash)
  super
  refresh
end

Instance Method Details

#absolute_source_fileObject



58
59
60
61
62
# File 'lib/polytrix/challenge.rb', line 58

def absolute_source_file
  return nil if source_file.nil?

  File.expand_path source_file, basedir
end

#action(what, &block) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/polytrix/challenge.rb', line 148

def action(what, &block)
  @state ||= state_file.read
  @state['last_attempted_action'] = what.to_s
  elapsed = Benchmark.measure do
    # synchronize_or_call(what, @state, &block)
    block.call(@state)
  end
  @state['last_completed_action'] = what.to_s
  elapsed
rescue Polytrix::FeatureNotImplementedError => e
  raise e
rescue ActionFailed => e
  log_failure(what, e)
  raise(ChallengeFailure, failure_message(what) +
    "  Please see .polytrix/logs/#{name}.log for more details",
        e.backtrace)
rescue Exception => e # rubocop:disable RescueException
  log_failure(what, e)
  raise ActionFailed,
        "Failed to complete ##{what} action: [#{e.message}]", e.backtrace
ensure
  KEYS_TO_PERSIST.each do |key|
    @state[key] = public_send(key)
  end
  state_file.write(@state) unless what == :destroy
end

#destroyObject



93
94
95
# File 'lib/polytrix/challenge.rb', line 93

def destroy
  transition_to :destroy
end

#destroy_actionObject



111
112
113
114
115
116
117
118
# File 'lib/polytrix/challenge.rb', line 111

def destroy_action
  perform_action(:destroy, 'Destroying') do
    @state_file.destroy
    @state_file = nil
    @state = {}
    refresh
  end
end

#detectObject



64
65
66
# File 'lib/polytrix/challenge.rb', line 64

def detect
  transition_to :detect
end

#detect_actionObject



68
69
70
71
72
73
74
# File 'lib/polytrix/challenge.rb', line 68

def detect_action
  perform_action(:detect, 'Detecting code sample') do
    fail FeatureNotImplementedError, "Implementor #{name} has not been cloned" unless implementor.cloned?
    fail FeatureNotImplementedError, name if source_file.nil?
    fail FeatureNotImplementedError, name unless File.exist?(absolute_source_file)
  end
end

#execObject



76
77
78
# File 'lib/polytrix/challenge.rb', line 76

def exec
  transition_to :exec
end

#exec_actionObject



80
81
82
83
84
85
86
87
# File 'lib/polytrix/challenge.rb', line 80

def exec_action
  perform_action(:exec, 'Executing') do
    fail FeatureNotImplementedError, "Implementor #{name} has not been cloned" unless implementor.cloned?
    fail FeatureNotImplementedError, name if source_file.nil?
    fail FeatureNotImplementedError, name unless File.exist?(absolute_source_file)
    self.result = challenge_runner.run_challenge self
  end
end

#failed?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'lib/polytrix/challenge.rb', line 175

def failed?
  last_attempted_action != last_completed_action
end

#failure_message(what) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a string explaining what action failed, at a high level. Used for displaying to end user.

Parameters:

  • what (String)

    an action

Returns:

  • (String)

    a failure message



279
280
281
# File 'lib/polytrix/challenge.rb', line 279

def failure_message(what)
  "#{what.capitalize} failed for test #{slug}."
end

#last_attempted_actionString

Returns the last successfully completed action state of the instance.

Returns:

  • (String)

    a named action which was last successfully completed



236
237
238
# File 'lib/polytrix/challenge.rb', line 236

def last_attempted_action
  state_file.read['last_attempted_action']
end

#last_completed_actionObject



240
241
242
# File 'lib/polytrix/challenge.rb', line 240

def last_completed_action
  state_file.read['last_completed_action']
end

#log_failure(what, _e) ⇒ Object



266
267
268
269
270
271
# File 'lib/polytrix/challenge.rb', line 266

def log_failure(what, _e)
  return if logger.logdev.nil?

  logger.logdev.error(failure_message(what))
  # Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
end

#loggerObject



50
51
52
# File 'lib/polytrix/challenge.rb', line 50

def logger
  implementor.logger
end

#perform_action(verb, output_verb) ⇒ Object



138
139
140
141
142
143
144
145
146
# File 'lib/polytrix/challenge.rb', line 138

def perform_action(verb, output_verb)
  banner "#{output_verb} #{slug}..."
  elapsed = action(verb) { yield }
  # elapsed = action(verb) { |state| driver.public_send(verb, state) }
  info("Finished #{output_verb.downcase} #{slug}" \
    " #{Util.duration(elapsed.real)}.")
  # yield if block_given?
  self
end

#refreshObject



39
40
41
42
43
44
# File 'lib/polytrix/challenge.rb', line 39

def refresh
  @state = state_file.read
  KEYS_TO_PERSIST.each do |key|
    public_send("#{key}=".to_sym, @state[key]) if @state[key]
  end
end

#sample?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/polytrix/challenge.rb', line 183

def sample?
  !source_file.nil?
end

#skipped?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/polytrix/challenge.rb', line 179

def skipped?
  result.nil?
end

#slugObject



54
55
56
# File 'lib/polytrix/challenge.rb', line 54

def slug
  slugify(suite, name, implementor.name)
end

#state_fileObject



35
36
37
# File 'lib/polytrix/challenge.rb', line 35

def state_file
  @state_file ||= StateFile.new(Dir.pwd, slug)
end

#statusObject



187
188
189
190
# File 'lib/polytrix/challenge.rb', line 187

def status
  status = last_attempted_action
  failed? ? "#{status}_failed" : status
end

#status_colorObject



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/polytrix/challenge.rb', line 216

def status_color
  case status_description
  when '<Not Found>' then :white
  when 'Cloned' then :magenta
  when 'Bootstrapped' then :magenta
  when 'Sample Found' then :cyan
  when 'Executed' then :blue
  when /Verified/
    if status_description =~ /Fully/
      :green
    else
      :yellow
    end
  else :red
  end
end

#status_descriptionObject



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

def status_description
  case status
  when 'clone' then 'Cloned'
  when 'clone_failed' then 'Clone Failed'
  when 'detect' then 'Sample Found'
  when 'detect_failed', nil then '<Not Found>'
  when 'bootstrap' then 'Bootstrapped'
  when 'bootstrap_failed' then 'Bootstrap Failed'
  when 'detect' then 'Detected'
  when 'exec' then 'Executed'
  when 'exec_failed' then 'Execution Failed'
  when 'verify', 'verify_failed'
    validator_count = validators.count
    validation_count = validations.values.select { |v| v['result'] == :passed }.count
    if validator_count == validation_count
      "Fully Verified (#{validation_count} of #{validator_count})"
    else
      "Partially Verified (#{validation_count} of #{validator_count})"
    end
  # when 'verify_failed' then 'Verification Failed'
  else "<Unknown (#{status})>"
  end
end

#test(_destroy_mode = :passing) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/polytrix/challenge.rb', line 97

def test(_destroy_mode = :passing)
  elapsed = Benchmark.measure do
    banner "Cleaning up any prior instances of #{slug}"
    destroy
    banner "Testing #{slug}"
    verify
    # destroy if destroy_mode == :passing
  end
  info "Finished testing #{slug} #{Util.duration(elapsed.real)}."
  self
  # ensure
  # destroy if destroy_mode == :always
end

#transition_to(desired) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/polytrix/challenge.rb', line 249

def transition_to(desired)
  transition_result = nil
  begin
    FSM.actions(last_completed_action, desired).each do |transition|
      transition_result = send("#{transition}_action")
    end
  rescue Polytrix::FeatureNotImplementedError
    warn("#{slug} is not implemented")
  rescue ActionFailed => e
    # Need to use with_friendly_errors again somewhere, since errors don't bubble up
    # without fast-fail?
    Polytrix.handle_error(e)
    raise(ChallengeFailure, e.message, e.backtrace)
  end
  transition_result
end

#validationsObject



244
245
246
247
# File 'lib/polytrix/challenge.rb', line 244

def validations
  return nil if result.nil?
  result.validations
end

#validatorsObject



46
47
48
# File 'lib/polytrix/challenge.rb', line 46

def validators
  Polytrix::ValidatorRegistry.validators_for self
end

#verifyObject



89
90
91
# File 'lib/polytrix/challenge.rb', line 89

def verify
  transition_to :verify
end

#verify_actionObject



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/polytrix/challenge.rb', line 120

def verify_action
  perform_action(:verify, 'Verifying') do
    validators.each do |validator|
      validation = validator.validate(self)
      status = case validation.result
               when :passed
                 Polytrix::Color.colorize("\u2713 Passed", :green)
               when :failed
                 Polytrix::Color.colorize('x Failed', :red)
                 Polytrix.handle_validation_failure(validation.error)
               else
                 Polytrix::Color.colorize(validation.result, :yellow)
               end
      info format('%-50s %s', validator.description, status)
    end
  end
end