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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +7 -2
- data/.travis.yml +2 -1
- data/Gemfile +1 -0
- data/README.md +190 -98
- data/Rakefile +8 -6
- data/bin/polytrix +2 -1
- data/docs/samples/code2doc/java/HelloWorld.md +4 -0
- data/docs/samples/code2doc/java/Quine.md +2 -0
- data/docs/samples/code2doc/python/hello_world.md +2 -0
- data/docs/samples/code2doc/python/quine.md +2 -0
- data/docs/samples/code2doc/ruby/hello_world.md +4 -0
- data/features/bootstrapping.feature +36 -0
- data/features/cloning.feature +34 -0
- data/features/execution.feature +2 -16
- data/features/fixtures/configs/empty.yml +12 -1
- data/features/fixtures/configs/hello_world.yml +11 -1
- data/features/fixtures/spec/polytrix_spec.rb +1 -4
- data/features/solo.feature +12 -0
- data/features/states.feature +40 -0
- data/features/step_definitions/sdk_steps.rb +11 -1
- data/features/support/env.rb +2 -1
- data/lib/polytrix/challenge.rb +211 -13
- data/lib/polytrix/challenge_result.rb +9 -0
- data/lib/polytrix/challenge_runner.rb +4 -11
- data/lib/polytrix/challenges.rb +16 -0
- data/lib/polytrix/cli/report.rb +0 -4
- data/lib/polytrix/cli.rb +229 -137
- data/lib/polytrix/color.rb +40 -0
- data/lib/polytrix/command/action.rb +26 -0
- data/lib/polytrix/command/list.rb +53 -0
- data/lib/polytrix/command/rundoc.rb +27 -0
- data/lib/polytrix/command/test.rb +24 -0
- data/lib/polytrix/command.rb +209 -0
- data/lib/polytrix/configuration.rb +30 -40
- data/lib/polytrix/core/file_system_helper.rb +2 -5
- data/lib/polytrix/core/hashie.rb +14 -0
- data/lib/polytrix/core/implementor.rb +52 -12
- data/lib/polytrix/core/manifest_section.rb +4 -0
- data/lib/polytrix/core/string_helpers.rb +15 -0
- data/lib/polytrix/documentation/helpers/code_helper.rb +3 -1
- data/lib/polytrix/error.rb +209 -0
- data/lib/polytrix/logger.rb +365 -8
- data/lib/polytrix/logging.rb +34 -0
- data/lib/polytrix/manifest.rb +40 -26
- data/lib/polytrix/result.rb +1 -0
- data/lib/polytrix/rspec.rb +7 -5
- data/lib/polytrix/runners/buff_shellout_executor.rb +19 -0
- data/lib/polytrix/runners/executor.rb +32 -0
- data/lib/polytrix/runners/mixlib_shellout_executor.rb +83 -0
- data/lib/polytrix/state_file.rb +60 -0
- data/lib/polytrix/util.rb +155 -0
- data/lib/polytrix/validation.rb +1 -1
- data/lib/polytrix/validator.rb +9 -5
- data/lib/polytrix/version.rb +1 -1
- data/lib/polytrix.rb +55 -33
- data/polytrix.gemspec +4 -2
- data/polytrix.rb +0 -5
- data/{polytrix_tests.yml → polytrix.yml} +5 -0
- data/samples/default_bootstrap.rb +0 -7
- data/samples/polytrix.rb +0 -9
- data/samples/{polytrix_tests.yml → polytrix.yml} +11 -0
- data/samples/polytrix_cli.sh +1 -1
- data/spec/fabricators/implementor_fabricator.rb +20 -0
- data/spec/fabricators/manifest_fabricator.rb +4 -1
- data/spec/fixtures/{polytrix_tests.yml → polytrix.yml} +10 -0
- data/spec/polytrix/challenge_runner_spec.rb +3 -2
- data/spec/polytrix/challenge_spec.rb +5 -4
- data/spec/polytrix/cli_spec.rb +23 -26
- data/spec/polytrix/configuration_spec.rb +4 -43
- data/spec/polytrix/documentation/helpers/code_helper_spec.rb +1 -1
- data/spec/polytrix/documentation_generator_spec.rb +2 -0
- data/spec/polytrix/implementor_spec.rb +44 -2
- data/spec/polytrix/manifest_spec.rb +7 -4
- data/spec/polytrix_spec.rb +9 -11
- data/spec/thor_spy.rb +2 -0
- metadata +66 -16
- data/features/fixtures/spec/polytrix_merge.rb +0 -5
- data/features/reporting.feature +0 -140
- data/lib/polytrix/executor.rb +0 -89
- 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
|
-
|
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}/
|
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
|
data/features/support/env.rb
CHANGED
data/lib/polytrix/challenge.rb
CHANGED
@@ -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 <
|
8
|
-
include
|
9
|
-
|
10
|
-
|
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
|
-
|
29
|
-
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
@@ -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
|
data/lib/polytrix/cli/report.rb
CHANGED
@@ -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 }
|