polytrix 0.1.0.pre → 0.1.0

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.
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 }