polytrix 0.1.0.pre → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +7 -2
  4. data/.travis.yml +2 -1
  5. data/Gemfile +1 -0
  6. data/README.md +190 -98
  7. data/Rakefile +8 -6
  8. data/bin/polytrix +2 -1
  9. data/docs/samples/code2doc/java/HelloWorld.md +4 -0
  10. data/docs/samples/code2doc/java/Quine.md +2 -0
  11. data/docs/samples/code2doc/python/hello_world.md +2 -0
  12. data/docs/samples/code2doc/python/quine.md +2 -0
  13. data/docs/samples/code2doc/ruby/hello_world.md +4 -0
  14. data/features/bootstrapping.feature +36 -0
  15. data/features/cloning.feature +34 -0
  16. data/features/execution.feature +2 -16
  17. data/features/fixtures/configs/empty.yml +12 -1
  18. data/features/fixtures/configs/hello_world.yml +11 -1
  19. data/features/fixtures/spec/polytrix_spec.rb +1 -4
  20. data/features/solo.feature +12 -0
  21. data/features/states.feature +40 -0
  22. data/features/step_definitions/sdk_steps.rb +11 -1
  23. data/features/support/env.rb +2 -1
  24. data/lib/polytrix/challenge.rb +211 -13
  25. data/lib/polytrix/challenge_result.rb +9 -0
  26. data/lib/polytrix/challenge_runner.rb +4 -11
  27. data/lib/polytrix/challenges.rb +16 -0
  28. data/lib/polytrix/cli/report.rb +0 -4
  29. data/lib/polytrix/cli.rb +229 -137
  30. data/lib/polytrix/color.rb +40 -0
  31. data/lib/polytrix/command/action.rb +26 -0
  32. data/lib/polytrix/command/list.rb +53 -0
  33. data/lib/polytrix/command/rundoc.rb +27 -0
  34. data/lib/polytrix/command/test.rb +24 -0
  35. data/lib/polytrix/command.rb +209 -0
  36. data/lib/polytrix/configuration.rb +30 -40
  37. data/lib/polytrix/core/file_system_helper.rb +2 -5
  38. data/lib/polytrix/core/hashie.rb +14 -0
  39. data/lib/polytrix/core/implementor.rb +52 -12
  40. data/lib/polytrix/core/manifest_section.rb +4 -0
  41. data/lib/polytrix/core/string_helpers.rb +15 -0
  42. data/lib/polytrix/documentation/helpers/code_helper.rb +3 -1
  43. data/lib/polytrix/error.rb +209 -0
  44. data/lib/polytrix/logger.rb +365 -8
  45. data/lib/polytrix/logging.rb +34 -0
  46. data/lib/polytrix/manifest.rb +40 -26
  47. data/lib/polytrix/result.rb +1 -0
  48. data/lib/polytrix/rspec.rb +7 -5
  49. data/lib/polytrix/runners/buff_shellout_executor.rb +19 -0
  50. data/lib/polytrix/runners/executor.rb +32 -0
  51. data/lib/polytrix/runners/mixlib_shellout_executor.rb +83 -0
  52. data/lib/polytrix/state_file.rb +60 -0
  53. data/lib/polytrix/util.rb +155 -0
  54. data/lib/polytrix/validation.rb +1 -1
  55. data/lib/polytrix/validator.rb +9 -5
  56. data/lib/polytrix/version.rb +1 -1
  57. data/lib/polytrix.rb +55 -33
  58. data/polytrix.gemspec +4 -2
  59. data/polytrix.rb +0 -5
  60. data/{polytrix_tests.yml → polytrix.yml} +5 -0
  61. data/samples/default_bootstrap.rb +0 -7
  62. data/samples/polytrix.rb +0 -9
  63. data/samples/{polytrix_tests.yml → polytrix.yml} +11 -0
  64. data/samples/polytrix_cli.sh +1 -1
  65. data/spec/fabricators/implementor_fabricator.rb +20 -0
  66. data/spec/fabricators/manifest_fabricator.rb +4 -1
  67. data/spec/fixtures/{polytrix_tests.yml → polytrix.yml} +10 -0
  68. data/spec/polytrix/challenge_runner_spec.rb +3 -2
  69. data/spec/polytrix/challenge_spec.rb +5 -4
  70. data/spec/polytrix/cli_spec.rb +23 -26
  71. data/spec/polytrix/configuration_spec.rb +4 -43
  72. data/spec/polytrix/documentation/helpers/code_helper_spec.rb +1 -1
  73. data/spec/polytrix/documentation_generator_spec.rb +2 -0
  74. data/spec/polytrix/implementor_spec.rb +44 -2
  75. data/spec/polytrix/manifest_spec.rb +7 -4
  76. data/spec/polytrix_spec.rb +9 -11
  77. data/spec/thor_spy.rb +2 -0
  78. metadata +66 -16
  79. data/features/fixtures/spec/polytrix_merge.rb +0 -5
  80. data/features/reporting.feature +0 -140
  81. data/lib/polytrix/executor.rb +0 -89
  82. data/samples/sdks/custom/polytrix.yml +0 -2
@@ -1,10 +1,7 @@
1
1
  require 'polytrix/rspec'
2
2
 
3
3
  Polytrix.configure do |polytrix|
4
- Dir['sdks/*'].each do |sdk|
5
- polytrix.implementor sdk
6
- end
7
- polytrix.test_manifest = 'polytrix_tests.yml'
4
+ polytrix.manifest = 'polytrix.yml'
8
5
  end
9
6
  Polytrix.bootstrap
10
7
  Polytrix.load_tests
@@ -0,0 +1,12 @@
1
+ Feature: Solo execution
2
+
3
+ Polytrix has a --solo mode for use if there is only a single implementor. It will infer some implementor settings and test scenarios so you can use Polytrix with minimal configuration.
4
+
5
+ In --solo mode, Polytrix will:
6
+ - Configure a single implementor based in the current working directory
7
+ - Auto-detect test scenarios based on file glob pattern
8
+
9
+ Scenario: Cloning all SDKs
10
+ Given the ruby SDK
11
+ When I run `bundle exec polytrix exec --solo=sdks/ruby`
12
+ Then the output should contain "Executing challenges-hello_world-ruby"
@@ -0,0 +1,40 @@
1
+ Feature: States
2
+
3
+ Scenario: Initial state
4
+ Given the ruby SDK
5
+ And the java SDK
6
+ And the python SDK
7
+ And the hello_world polytrix config
8
+ And the standard rspec setup
9
+ When I run `bundle exec polytrix list`
10
+ Then the output should contain:
11
+ """
12
+ Suite Scenario Implementor Status
13
+ Katas hello world ruby <Not Found>
14
+ Katas hello world java <Not Found>
15
+ Katas hello world python <Not Found>
16
+ """
17
+
18
+ @no-clobber
19
+ Scenario: State after execution
20
+ Given I run `bundle exec polytrix exec python`
21
+ When I run `bundle exec polytrix list`
22
+ Then the output should contain:
23
+ """
24
+ Suite Scenario Implementor Status
25
+ Katas hello world ruby <Not Found>
26
+ Katas hello world java <Not Found>
27
+ Katas hello world python Executed
28
+ """
29
+
30
+ @no-clobber
31
+ Scenario: State after verification
32
+ Given I run `bundle exec polytrix verify ruby`
33
+ When I run `bundle exec polytrix list`
34
+ Then the output should contain:
35
+ """
36
+ Suite Scenario Implementor Status
37
+ Katas hello world ruby Verified (Level 0)
38
+ Katas hello world java <Not Found>
39
+ Katas hello world python Executed
40
+ """
@@ -1,12 +1,22 @@
1
+ require 'yaml'
2
+
1
3
  Given(/^the (\w+) SDK$/) do |sdk|
2
4
  FileUtils.mkdir_p "#{current_dir}/sdks"
3
5
  FileUtils.cp_r "samples/sdks/#{sdk}", "#{current_dir}/sdks"
4
6
  end
5
7
 
6
8
  Given(/^the (\w+) polytrix config$/) do |config|
7
- FileUtils.cp_r "features/fixtures/configs/#{config}.yml", "#{current_dir}/polytrix_tests.yml"
9
+ FileUtils.cp_r "features/fixtures/configs/#{config}.yml", "#{current_dir}/polytrix.yml"
8
10
  end
9
11
 
10
12
  Given(/^the standard rspec setup$/) do
11
13
  FileUtils.cp_r 'features/fixtures/spec/', "#{current_dir}/"
12
14
  end
15
+
16
+ Then(/^the file "(.*?)" should contain yaml matching:$/) do |file, content|
17
+ in_current_dir do
18
+ actual_content = YAML.load(File.read(file))
19
+ expected_content = YAML.load(content)
20
+ expect(actual_content).to eq(expected_content)
21
+ end
22
+ end
@@ -2,7 +2,8 @@ require 'simplecov'
2
2
  SimpleCov.start
3
3
 
4
4
  require 'aruba/cucumber'
5
+ require 'aruba/jruby'
5
6
 
6
7
  Before do
7
- @aruba_timeout_seconds = 20
8
+ @aruba_timeout_seconds = RUBY_PLATFORM == 'java' ? 60 : 30
8
9
  end
@@ -1,18 +1,20 @@
1
+ require 'benchmark'
1
2
  require 'hashie/dash'
2
3
  require 'hashie/extensions/coercion'
3
4
  require 'hashie/extensions/indifferent_access'
4
5
  require 'polytrix/documentation/helpers/code_helper'
5
6
 
6
7
  module Polytrix
7
- class Challenge < Hashie::Dash
8
- include Hashie::Extensions::Coercion
9
-
10
- # View heleprs
8
+ class Challenge < Polytrix::Dash # rubocop:disable ClassLength
9
+ include Polytrix::Core::FileSystemHelper
10
+ include Polytrix::Logging
11
+ include Polytrix::StringHelpers
12
+ # View helpers
11
13
  include Polytrix::Documentation::Helpers::CodeHelper
12
14
 
13
15
  property :name
14
- property :description
15
16
  property :implementor
17
+ coerce_key :implementor, Polytrix::Implementor
16
18
  property :suite, required: true
17
19
  property :vars, default: {}
18
20
  property :source_file
@@ -21,20 +23,216 @@ module Polytrix
21
23
  coerce_key :basedir, Pathname
22
24
  property :challenge_runner, default: ChallengeRunner.create_runner
23
25
  property :result
26
+ # coerce_key :results, Array[ChallengeResult]
24
27
  property :env_file
25
28
  # coerce_key :vars, Polytrix::Manifest::Environment
26
29
  property :plugin_data, default: {}
30
+ property :verification_level, default: 0
31
+
32
+ def state_file
33
+ @state_file ||= StateFile.new(Dir.pwd, slug)
34
+ end
35
+
36
+ def logger
37
+ implementor.logger
38
+ end
39
+
40
+ def slug
41
+ slugify("#{suite}-#{name}-#{implementor.name}")
42
+ end
43
+
44
+ def absolute_source_file
45
+ return nil if source_file.nil?
46
+
47
+ File.expand_path source_file, basedir
48
+ end
49
+
50
+ def exec
51
+ transition_to :exec
52
+ end
53
+
54
+ def exec_action
55
+ perform_action(:exec, 'Executing') do
56
+ fail FeatureNotImplementedError, "Implementor #{name} has not been cloned" unless implementor.cloned?
57
+ fail FeatureNotImplementedError, name if source_file.nil?
58
+ fail FeatureNotImplementedError, name unless File.exists?(absolute_source_file)
59
+ self.result = challenge_runner.run_challenge self
60
+ end
61
+ end
62
+
63
+ def verify
64
+ transition_to :verify
65
+ end
66
+
67
+ def destroy
68
+ transition_to :destroy
69
+ end
70
+
71
+ def test(destroy_mode = :passing)
72
+ elapsed = Benchmark.measure do
73
+ banner "Cleaning up any prior instances of #{slug}"
74
+ destroy
75
+ banner "Testing #{slug}"
76
+ verify
77
+ # destroy if destroy_mode == :passing
78
+ end
79
+ info "Finished testing #{slug} #{Util.duration(elapsed.real)}."
80
+ self
81
+ # ensure
82
+ # destroy if destroy_mode == :always
83
+ end
84
+
85
+ def code2doc
86
+ if source_file.nil?
87
+ warn "No code sample available for #{slug}, no documentation will be generated."
88
+ return
89
+ end
27
90
 
28
- def run
29
- @result = challenge_runner.run_challenge self
91
+ display_file = relativize(absolute_source_file, Dir.pwd)
92
+ banner "Generating documentation from #{display_file}"
93
+ target_dir = Polytrix.configuration.documentation_dir
94
+ format = Polytrix.configuration.documentation_format
95
+ # language = options[:lang]
96
+ language = '.rb'
97
+ target_file_name = File.basename(source_file, File.extname(source_file)) + ".#{format}"
98
+ target_file = File.join(target_dir, target_file_name)
99
+ doc = Polytrix::DocumentationGenerator.new.code2doc(absolute_source_file, language)
100
+ FileUtils.mkdir_p File.dirname(target_file)
101
+ File.write(target_file, doc)
102
+ info "Documentated saved to #{target_file}"
103
+ rescue Polytrix::Documentation::CommentStyles::UnknownStyleError => e
104
+ abort "Unknown file extension: #{e.extension}, please use --lang to set the language manually"
30
105
  end
31
106
 
32
- def validate
33
- run unless @result
34
- # validators = Polytrix::ValidatorRegistry.validators_for self
35
- # validators.each do |validator|
36
- # validator.validate self
37
- # end
107
+ def destroy_action
108
+ perform_action(:destroy, 'Destroying') do
109
+ # @state_file.destroy
110
+ # @state_file = nil
111
+ end
112
+ @state_file.destroy
113
+ @state_file = nil
114
+ end
115
+
116
+ def verify_action
117
+ perform_action(:verify, 'Verifying') do
118
+ validators = Polytrix::ValidatorRegistry.validators_for self
119
+ validators.each do |validator|
120
+ validator.validate self
121
+ end
122
+ end
123
+ end
124
+
125
+ def perform_action(verb, output_verb)
126
+ banner "#{output_verb} #{slug}..."
127
+ elapsed = action(verb) { yield }
128
+ # elapsed = action(verb) { |state| driver.public_send(verb, state) }
129
+ info("Finished #{output_verb.downcase} #{slug}" \
130
+ " #{Util.duration(elapsed.real)}.")
131
+ # yield if block_given?
132
+ self
133
+ end
134
+
135
+ def action(what, &block)
136
+ state = state_file.read
137
+ elapsed = Benchmark.measure do
138
+ # synchronize_or_call(what, state, &block)
139
+ block.call(state)
140
+ end
141
+ state['last_action'] = what.to_s
142
+ elapsed
143
+ rescue Polytrix::FeatureNotImplementedError => e
144
+ raise e
145
+ rescue ActionFailed => e
146
+ log_failure(what, e)
147
+ fail(ChallengeFailure, failure_message(what) +
148
+ " Please see .polytrix/logs/#{name}.log for more details",
149
+ e.backtrace)
150
+ rescue Exception => e # rubocop:disable RescueException
151
+ log_failure(what, e)
152
+ fail ActionFailed,
153
+ "Failed to complete ##{what} action: [#{e.message}]", e.backtrace
154
+ ensure
155
+ state_file.write(state)
156
+ end
157
+
158
+ # Returns the last successfully completed action state of the instance.
159
+ #
160
+ # @return [String] a named action which was last successfully completed
161
+ def last_action
162
+ state_file.read['last_action']
163
+ end
164
+
165
+ def transition_to(desired)
166
+ transition_result = nil
167
+ begin
168
+ FSM.actions(last_action, desired).each do |transition|
169
+ transition_result = send("#{transition}_action")
170
+ end
171
+ rescue Polytrix::FeatureNotImplementedError
172
+ warn("#{slug} is not implemented")
173
+ rescue ActionFailed => e
174
+ error("#{slug} failed: #{e}")
175
+ fail(ChallengeFailure, e.message, e.backtrace)
176
+ end
177
+ transition_result
178
+ end
179
+
180
+ def log_failure(what, e)
181
+ return if logger.logdev.nil?
182
+
183
+ logger.logdev.error(failure_message(what))
184
+ # Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
185
+ end
186
+
187
+ # Returns a string explaining what action failed, at a high level. Used
188
+ # for displaying to end user.
189
+ #
190
+ # @param what [String] an action
191
+ # @return [String] a failure message
192
+ # @api private
193
+ def failure_message(what)
194
+ "#{what.capitalize} failed on instance #{slug}."
195
+ end
196
+
197
+ # The simplest finite state machine pseudo-implementation needed to manage
198
+ # an Instance.
199
+ #
200
+ # @api private
201
+ class FSM
202
+ # Returns an Array of all transitions to bring an Instance from its last
203
+ # reported transistioned state into the desired transitioned state.
204
+ #
205
+ # @param last [String,Symbol,nil] the last known transitioned state of
206
+ # the Instance, defaulting to `nil` (for unknown or no history)
207
+ # @param desired [String,Symbol] the desired transitioned state for the
208
+ # Instance
209
+ # @return [Array<Symbol>] an Array of transition actions to perform
210
+ # @api private
211
+ def self.actions(last = nil, desired)
212
+ last_index = index(last)
213
+ desired_index = index(desired)
214
+
215
+ if last_index == desired_index || last_index > desired_index
216
+ Array(TRANSITIONS[desired_index])
217
+ else
218
+ TRANSITIONS.slice(last_index + 1, desired_index - last_index)
219
+ end
220
+ end
221
+
222
+ TRANSITIONS = [:destroy, :exec, :verify]
223
+
224
+ # Determines the index of a state in the state lifecycle vector. Woah.
225
+ #
226
+ # @param transition [Symbol,#to_sym] a state
227
+ # @param [Integer] the index position
228
+ # @api private
229
+ def self.index(transition)
230
+ if transition.nil?
231
+ 0
232
+ else
233
+ TRANSITIONS.find_index { |t| t == transition.to_sym }
234
+ end
235
+ end
38
236
  end
39
237
  end
40
238
  end
@@ -0,0 +1,9 @@
1
+ require 'hashie/dash'
2
+ require 'hashie/extensions/coercion'
3
+ require 'hashie/extensions/indifferent_access'
4
+ require 'polytrix/documentation/helpers/code_helper'
5
+
6
+ module Polytrix
7
+ class ChallengeResult < Polytrix::Dash
8
+ end
9
+ end
@@ -1,5 +1,4 @@
1
1
  require 'polytrix'
2
- require 'mixlib/shellout'
3
2
  require 'rbconfig'
4
3
 
5
4
  module Polytrix
@@ -10,7 +9,7 @@ module Polytrix
10
9
 
11
10
  class ChallengeRunner < Thor::Shell::Color
12
11
  include Polytrix::Core::FileSystemHelper
13
- include Polytrix::Executor
12
+ include Polytrix::Runners::Executor
14
13
 
15
14
  def self.create_runner
16
15
  case RbConfig::CONFIG['host_os']
@@ -21,14 +20,6 @@ module Polytrix
21
20
  end
22
21
  end
23
22
 
24
- def editor_enabled?
25
- !challenge_editor.nil?
26
- end
27
-
28
- def challenge_editor
29
- ENV['CHALLENGE_EDITOR']
30
- end
31
-
32
23
  def run_command(command)
33
24
  if Polytrix.configuration.dry_run
34
25
  puts "Would have run #{command}"
@@ -39,8 +30,10 @@ module Polytrix
39
30
  end
40
31
 
41
32
  def run_challenge(challenge)
33
+ # Logging.mdc['implementor'] = "\033[35m#{challenge.implementor.name}\033[0m"
34
+ # Logging.mdc['scenario'] = "\033[32m#{challenge.name}\033[0m"
42
35
  middleware.call(challenge)
43
- challenge
36
+ challenge.result
44
37
  end
45
38
 
46
39
  private
@@ -0,0 +1,16 @@
1
+ module Polytrix
2
+ class Challenges < Hash
3
+ def get(regex)
4
+ _, v = find do |k, _|
5
+ regex.match k
6
+ end
7
+ v
8
+ end
9
+
10
+ def get_all(regex)
11
+ select do |k, _|
12
+ regex.match k
13
+ end.values
14
+ end
15
+ end
16
+ end
@@ -48,10 +48,6 @@ module Polytrix
48
48
 
49
49
  protected
50
50
 
51
- def matrix_data
52
- @matrix ||= Polytrix::Manifest.new(YAML.load(Polytrix.merge_results(Dir['reports/test_report*.yaml'])))
53
- end
54
-
55
51
  def load_results
56
52
  result_stats = Hash.new do |hash, sdk|
57
53
  hash[sdk] = { passed: 0, failed: 0, pending: 0, skipped: 0 }