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