polytrix 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop-todo.yml +14 -5
- data/Gemfile +2 -1
- data/README.md +139 -177
- data/Rakefile +5 -12
- data/bin/polytrix +0 -1
- data/features/bootstrapping.feature +0 -3
- data/features/cloning.feature +0 -3
- data/features/show.feature +38 -0
- data/features/states.feature +12 -13
- data/features/step_definitions/sdk_steps.rb +0 -4
- data/lib/polytrix/challenge.rb +135 -53
- data/lib/polytrix/challenge_result.rb +0 -2
- data/lib/polytrix/challenge_runner.rb +28 -18
- data/lib/polytrix/cli.rb +53 -69
- data/lib/polytrix/color.rb +2 -2
- data/lib/polytrix/command/action.rb +4 -3
- data/lib/polytrix/command/list.rb +39 -28
- data/lib/polytrix/command/report.rb +9 -86
- data/lib/polytrix/command/reports/code2doc.rb +72 -0
- data/lib/polytrix/command/reports/dashboard.rb +125 -0
- data/lib/polytrix/command/show.rb +148 -0
- data/lib/polytrix/command.rb +37 -104
- data/lib/polytrix/configuration.rb +14 -18
- data/lib/polytrix/{core/hashie.rb → dash.rb} +4 -3
- data/lib/polytrix/documentation/code_segmenter.rb +8 -8
- data/lib/polytrix/documentation/comment_styles.rb +1 -1
- data/lib/polytrix/documentation/helpers/code_helper.rb +9 -0
- data/lib/polytrix/documentation_generator.rb +11 -14
- data/lib/polytrix/error.rb +104 -97
- data/lib/polytrix/executor.rb +33 -0
- data/lib/polytrix/{runners → executors}/buff_shellout_executor.rb +1 -1
- data/lib/polytrix/executors/linux_challenge_executor.rb +29 -0
- data/lib/polytrix/executors/mixlib_shellout_executor.rb +55 -0
- data/lib/polytrix/{runners/windows_challenge_runner.rb → executors/windows_challenge_executor.rb} +4 -11
- data/lib/polytrix/{core/implementor.rb → implementor.rb} +10 -6
- data/lib/polytrix/manifest.rb +2 -31
- data/lib/polytrix/{reports → reporters}/hash_reporter.rb +6 -2
- data/lib/polytrix/{reports → reporters}/json_reporter.rb +2 -2
- data/lib/polytrix/{reports → reporters}/markdown_reporter.rb +7 -2
- data/lib/polytrix/{reports → reporters}/yaml_reporter.rb +2 -2
- data/lib/polytrix/reporters.rb +27 -0
- data/lib/polytrix/result.rb +6 -5
- data/lib/polytrix/spies/file_system_spy.rb +15 -0
- data/lib/polytrix/spies.rb +61 -0
- data/lib/polytrix/state_file.rb +1 -20
- data/lib/polytrix/util.rb +157 -62
- data/lib/polytrix/validation.rb +41 -2
- data/lib/polytrix/validator.rb +9 -4
- data/lib/polytrix/version.rb +1 -1
- data/lib/polytrix.rb +110 -105
- data/polytrix.gemspec +7 -2
- data/polytrix.yml +16 -13
- data/resources/assets/pygments/autumn.css +58 -0
- data/resources/assets/pygments/borland.css +46 -0
- data/resources/assets/pygments/bw.css +34 -0
- data/resources/assets/pygments/colorful.css +61 -0
- data/resources/assets/pygments/default.css +62 -0
- data/resources/assets/pygments/emacs.css +61 -0
- data/resources/assets/pygments/friendly.css +61 -0
- data/resources/assets/pygments/fruity.css +69 -0
- data/resources/assets/pygments/github.css +61 -0
- data/resources/assets/pygments/manni.css +61 -0
- data/resources/assets/pygments/monokai.css +64 -0
- data/resources/assets/pygments/murphy.css +61 -0
- data/resources/assets/pygments/native.css +69 -0
- data/resources/assets/pygments/pastie.css +60 -0
- data/resources/assets/pygments/perldoc.css +58 -0
- data/resources/assets/pygments/tango.css +69 -0
- data/resources/assets/pygments/trac.css +59 -0
- data/resources/assets/pygments/vim.css +69 -0
- data/resources/assets/pygments/vs.css +33 -0
- data/resources/assets/pygments/zenburn.css +1 -0
- data/resources/assets/style.css +41 -0
- data/resources/templates/dashboard/files/dashboard.html.tt +82 -0
- data/resources/templates/dashboard/templates/_test_report.html.tt +87 -0
- data/samples/bootstrap.sh +2 -0
- data/samples/clone.sh +2 -0
- data/samples/code2doc.sh +4 -0
- data/samples/docs/samples/code2doc/java/katas-hello_world-java.md +17 -0
- data/samples/docs/samples/code2doc/java/katas-quine-java.md +35 -0
- data/samples/docs/samples/code2doc/python/katas-hello_world-python.md +5 -0
- data/samples/docs/samples/code2doc/python/katas-quine-python.md +6 -0
- data/samples/docs/samples/code2doc/ruby/katas-hello_world-ruby.md +11 -0
- data/samples/exec.sh +2 -0
- data/samples/polytrix.rb +2 -2
- data/samples/polytrix.yml +5 -2
- data/samples/show.sh +4 -0
- data/samples/test.sh +2 -0
- data/samples/tests/polytrix/validators.rb +2 -2
- data/samples/verify.sh +3 -0
- data/scripts/wrapper +4 -7
- data/spec/fabricators/challenge_fabricator.rb +2 -9
- data/spec/fabricators/implementor_fabricator.rb +0 -8
- data/spec/fabricators/manifest_fabricator.rb +2 -9
- data/spec/fabricators/validator_fabricator.rb +2 -4
- data/spec/polytrix/challenge_runner_spec.rb +20 -0
- data/spec/polytrix/documentation/helpers/code_helper_spec.rb +7 -7
- data/spec/polytrix/file_finder_spec.rb +5 -5
- data/spec/polytrix/manifest_spec.rb +0 -21
- data/spec/polytrix/result_spec.rb +14 -14
- data/spec/polytrix/validator_registry_spec.rb +4 -4
- data/spec/polytrix/validator_spec.rb +9 -9
- data/spec/polytrix_spec.rb +1 -25
- data/spec/spec_helper.rb +8 -1
- metadata +130 -38
- data/features/execution.feature +0 -53
- data/features/fixtures/spec/polytrix_spec.rb +0 -7
- data/lib/polytrix/cli/report.rb +0 -84
- data/lib/polytrix/command/rundoc.rb +0 -27
- data/lib/polytrix/core/file_system_helper.rb +0 -75
- data/lib/polytrix/core/manifest_section.rb +0 -4
- data/lib/polytrix/core/string_helpers.rb +0 -15
- data/lib/polytrix/documentation/view_helper.rb +0 -21
- data/lib/polytrix/rspec/documentation_formatter.rb +0 -66
- data/lib/polytrix/rspec/yaml_report.rb +0 -51
- data/lib/polytrix/rspec.rb +0 -56
- data/lib/polytrix/runners/executor.rb +0 -34
- data/lib/polytrix/runners/linux_challenge_runner.rb +0 -23
- data/lib/polytrix/runners/middleware/change_directory.rb +0 -20
- data/lib/polytrix/runners/middleware/feature_executor.rb +0 -24
- data/lib/polytrix/runners/middleware/setup_env_vars.rb +0 -42
- data/lib/polytrix/runners/mixlib_shellout_executor.rb +0 -83
- data/lib/polytrix/validations.rb +0 -23
- data/samples/scripts/wrapper +0 -7
- data/spec/polytrix/middleware/feature_executor_spec.rb +0 -48
- data/spec/polytrix/validations_spec.rb +0 -16
data/lib/polytrix/util.rb
CHANGED
@@ -1,20 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Copyright (C) 2012, 2013, 2014, Fletcher Nichol
|
6
|
-
#
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
-
# you may not use this file except in compliance with the License.
|
9
|
-
# You may obtain a copy of the License at
|
10
|
-
#
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
-
# See the License for the specific language governing permissions and
|
17
|
-
# limitations under the License.
|
3
|
+
# Much of this code has been adapted from Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
+
# work on test-kitchen.
|
18
5
|
|
19
6
|
module Polytrix
|
20
7
|
# Stateless utility methods used in different contexts. Essentially a mini
|
@@ -59,14 +46,12 @@ module Polytrix
|
|
59
46
|
# @return [Object] a converted hash with all keys as symbols
|
60
47
|
def self.symbolized_hash(obj)
|
61
48
|
if obj.is_a?(Hash)
|
62
|
-
obj.
|
49
|
+
obj.each_with_object({}) do |(k, v), h|
|
63
50
|
h[k.to_sym] = symbolized_hash(v)
|
64
|
-
h
|
65
51
|
end
|
66
52
|
elsif obj.is_a?(Array)
|
67
|
-
obj.
|
53
|
+
obj.each_with_object([]) do |e, a|
|
68
54
|
a << symbolized_hash(e)
|
69
|
-
a
|
70
55
|
end
|
71
56
|
else
|
72
57
|
obj
|
@@ -82,14 +67,12 @@ module Polytrix
|
|
82
67
|
# @return [Object] a converted hash with all keys as strings
|
83
68
|
def self.stringified_hash(obj)
|
84
69
|
if obj.is_a?(Hash)
|
85
|
-
obj.
|
70
|
+
obj.each_with_object({}) do |(k, v), h|
|
86
71
|
h[k.to_s] = stringified_hash(v)
|
87
|
-
h
|
88
72
|
end
|
89
73
|
elsif obj.is_a?(Array)
|
90
|
-
obj.
|
74
|
+
obj.each_with_object([]) do |e, a|
|
91
75
|
a << stringified_hash(e)
|
92
|
-
a
|
93
76
|
end
|
94
77
|
else
|
95
78
|
obj
|
@@ -107,49 +90,161 @@ module Polytrix
|
|
107
90
|
format('(%dm%.2fs)', minutes, seconds)
|
108
91
|
end
|
109
92
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
93
|
+
module String
|
94
|
+
module ClassMethods
|
95
|
+
def slugify(*labels)
|
96
|
+
labels.map do |label|
|
97
|
+
label.downcase.gsub(/[\.\s-]/, '_')
|
98
|
+
end.join('-')
|
99
|
+
end
|
100
|
+
|
101
|
+
def ansi2html(text)
|
102
|
+
HTML.from_ansi(text)
|
103
|
+
end
|
104
|
+
|
105
|
+
def escape_html(text)
|
106
|
+
HTML.escape_html(text)
|
107
|
+
end
|
108
|
+
alias_method :h, :escape_html
|
109
|
+
|
110
|
+
def highlight(source, opts = {})
|
111
|
+
return nil if source.nil?
|
112
|
+
|
113
|
+
opts[:language] ||= 'ruby'
|
114
|
+
opts[:formatter] ||= 'terminal256'
|
115
|
+
Highlight.new(opts).highlight(source)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.included(base)
|
120
|
+
base.extend(ClassMethods)
|
121
|
+
end
|
122
|
+
|
123
|
+
include ClassMethods
|
124
124
|
end
|
125
125
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
# STRING
|
136
|
-
# Util.outdent!(string) # => "a\n b\nc\n"
|
137
|
-
#
|
138
|
-
# @param string [String] the string that will be modified
|
139
|
-
# @return [String] the modified string
|
140
|
-
def self.outdent!(string)
|
141
|
-
string.gsub!(/^ {#{string.index(/[^ ]/)}}/, '')
|
126
|
+
class Highlight
|
127
|
+
def initialize(opts)
|
128
|
+
@lexer = Rouge::Lexer.find(opts[:language]) || Rouge::Lexer.guess_by_filename(opts[:filename])
|
129
|
+
@formatter = opts[:formatter]
|
130
|
+
end
|
131
|
+
|
132
|
+
def highlight(source)
|
133
|
+
Rouge.highlight(source, @lexer, @formatter)
|
134
|
+
end
|
142
135
|
end
|
143
136
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
137
|
+
class HTML
|
138
|
+
ANSICODES = {
|
139
|
+
'1' => 'bold',
|
140
|
+
'4' => 'underline',
|
141
|
+
'30' => 'black',
|
142
|
+
'31' => 'red',
|
143
|
+
'32' => 'green',
|
144
|
+
'33' => 'yellow',
|
145
|
+
'34' => 'blue',
|
146
|
+
'35' => 'magenta',
|
147
|
+
'36' => 'cyan',
|
148
|
+
'37' => 'white',
|
149
|
+
'40' => 'bg-black',
|
150
|
+
'41' => 'bg-red',
|
151
|
+
'42' => 'bg-green',
|
152
|
+
'43' => 'bg-yellow',
|
153
|
+
'44' => 'bg-blue',
|
154
|
+
'45' => 'bg-magenta',
|
155
|
+
'46' => 'bg-cyan',
|
156
|
+
'47' => 'bg-white'
|
157
|
+
}
|
158
|
+
|
159
|
+
def self.from_ansi(text)
|
160
|
+
ansi = StringScanner.new(text)
|
161
|
+
html = StringIO.new
|
162
|
+
until ansi.eos?
|
163
|
+
if ansi.scan(/\e\[0?m/)
|
164
|
+
html.print(%(</span>))
|
165
|
+
elsif ansi.scan(/\e\[0?(\d+)m/)
|
166
|
+
# use class instead of style?
|
167
|
+
style = ANSICODES[ansi[1]] || 'text-reset'
|
168
|
+
html.print(%(<span class="#{style}">))
|
169
|
+
else
|
170
|
+
html.print(ansi.scan(/./m))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
html.string
|
174
|
+
end
|
175
|
+
|
176
|
+
# From Rack
|
177
|
+
|
178
|
+
ESCAPE_HTML = {
|
179
|
+
'&' => '&',
|
180
|
+
'<' => '<',
|
181
|
+
'>' => '>',
|
182
|
+
"'" => ''',
|
183
|
+
'"' => '"',
|
184
|
+
'/' => '/'
|
185
|
+
}
|
186
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
187
|
+
|
188
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
189
|
+
def self.escape_html(string)
|
190
|
+
string.to_s.gsub(ESCAPE_HTML_PATTERN) { |c| ESCAPE_HTML[c] }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
module FileSystem
|
195
|
+
include Polytrix::Logging
|
196
|
+
include Polytrix::Util::String
|
197
|
+
|
198
|
+
# Finds a file by loosely matching the file name to a scenario name
|
199
|
+
def find_file(search_path, scenario_name, ignored_patterns = nil)
|
200
|
+
ignored_patterns ||= read_gitignore(search_path)
|
201
|
+
glob_string = "#{search_path}/**/*#{slugify(scenario_name)}.*"
|
202
|
+
potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
|
203
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
|
204
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
|
205
|
+
|
206
|
+
# Filter out ignored filesFind the first file, not including generated files
|
207
|
+
files = potential_files.select do |f|
|
208
|
+
!ignored? ignored_patterns, search_path, f
|
209
|
+
end
|
210
|
+
|
211
|
+
# Select the shortest path, likely the best match
|
212
|
+
file = files.min_by(&:length)
|
213
|
+
|
214
|
+
fail Errno::ENOENT, "No file was found for #{scenario_name} within #{search_path}" if file.nil?
|
215
|
+
Pathname.new file
|
216
|
+
end
|
217
|
+
|
218
|
+
def relativize(file, base_path)
|
219
|
+
absolute_file = File.absolute_path(file)
|
220
|
+
absolute_base_path = File.absolute_path(base_path)
|
221
|
+
Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
# @api private
|
227
|
+
def read_gitignore(dir)
|
228
|
+
gitignore_file = "#{dir}/.gitignore"
|
229
|
+
File.read(gitignore_file)
|
230
|
+
rescue
|
231
|
+
''
|
232
|
+
end
|
233
|
+
|
234
|
+
# @api private
|
235
|
+
def ignored?(ignored_patterns, base_path, target_file)
|
236
|
+
# Trying to match the git ignore rules but there's some discrepencies.
|
237
|
+
ignored_patterns.split.find do |pattern|
|
238
|
+
# if git ignores a folder, we should ignore all files it contains
|
239
|
+
pattern = "#{pattern}**" if pattern[-1] == '/'
|
240
|
+
started_with_slash = pattern.start_with? '/'
|
241
|
+
|
242
|
+
pattern.gsub!(/\A\//, '') # remove leading slashes since we're searching from root
|
243
|
+
file = relativize(target_file, base_path)
|
244
|
+
ignored = file.fnmatch? pattern
|
245
|
+
ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
|
246
|
+
end
|
247
|
+
end
|
153
248
|
end
|
154
249
|
end
|
155
250
|
end
|
data/lib/polytrix/validation.rb
CHANGED
@@ -2,19 +2,58 @@ require 'hashie/dash'
|
|
2
2
|
|
3
3
|
module Polytrix
|
4
4
|
class Validation < Polytrix::ManifestSection
|
5
|
+
# TODO: Should we have (expectation) 'failed' vs (unexpected) 'error'?
|
5
6
|
ALLOWABLE_STATES = %w(passed pending failed skipped)
|
6
|
-
|
7
|
-
property :result
|
7
|
+
|
8
|
+
property :result, required: true
|
9
|
+
property :error
|
8
10
|
|
9
11
|
def result=(state)
|
12
|
+
state = state.to_s
|
10
13
|
fail invalidate_state_error unless ALLOWABLE_STATES.include? state
|
11
14
|
super
|
12
15
|
end
|
13
16
|
|
17
|
+
ALLOWABLE_STATES.each do |state|
|
18
|
+
define_method "#{state}?" do
|
19
|
+
result == state?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Gets the source of the validation code block where a ValidationFailure occurred.
|
24
|
+
def error_source
|
25
|
+
return nil if error.nil?
|
26
|
+
source_from_error(error)
|
27
|
+
end
|
28
|
+
|
14
29
|
protected
|
15
30
|
|
16
31
|
def invalidate_state_error(state)
|
17
32
|
ArgumentError.new "Invalid result state: #{state}, should be one of #{ALLOWABLE_STATES.inspect}"
|
18
33
|
end
|
34
|
+
|
35
|
+
def source_from_error(e)
|
36
|
+
error_location = e.backtrace_locations.delete_if { |l| l.absolute_path =~ /gems\/rspec-/ }.first
|
37
|
+
error_source = File.read(error_location.absolute_path)
|
38
|
+
error_lineno = error_location.lineno - 1 # lineno counts from 1
|
39
|
+
get_dedented_block(error_source, error_lineno)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_dedented_block(source_text, target_lineno)
|
43
|
+
block = []
|
44
|
+
lines = source_text.lines
|
45
|
+
target_indent = lines[target_lineno][/\A */].size
|
46
|
+
lines[0...target_lineno].reverse.each do |line|
|
47
|
+
indent = line[/\A */].size
|
48
|
+
block.prepend line
|
49
|
+
break if indent < target_indent
|
50
|
+
end
|
51
|
+
lines[target_lineno..lines.size].each do |line|
|
52
|
+
indent = line[/\A */].size
|
53
|
+
block.push line
|
54
|
+
break if indent < target_indent
|
55
|
+
end
|
56
|
+
block.join
|
57
|
+
end
|
19
58
|
end
|
20
59
|
end
|
data/lib/polytrix/validator.rb
CHANGED
@@ -5,21 +5,26 @@ module Polytrix
|
|
5
5
|
include RSpec::Matchers
|
6
6
|
|
7
7
|
UNIVERSAL_MATCHER = //
|
8
|
-
attr_reader :description, :suite, :
|
8
|
+
attr_reader :description, :suite, :scenario, :level, :callback
|
9
9
|
|
10
10
|
def initialize(description, scope = {}, &validator)
|
11
11
|
@description = description
|
12
12
|
@suite = scope[:suite] ||= UNIVERSAL_MATCHER
|
13
|
-
@
|
13
|
+
@scenario = scope[:scenario] ||= UNIVERSAL_MATCHER
|
14
14
|
@callback = validator
|
15
15
|
end
|
16
16
|
|
17
17
|
def should_validate?(challenge)
|
18
|
-
|
18
|
+
# TODO: Case-insensitive matching?
|
19
|
+
!!(@suite.match(challenge.suite.to_s) && @scenario.match(challenge.name.to_s)) # rubocop:disable Style/DoubleNegation
|
19
20
|
end
|
20
21
|
|
21
22
|
def validate(challenge)
|
22
|
-
instance_exec
|
23
|
+
instance_exec(challenge, &@callback) if should_validate?(challenge)
|
24
|
+
challenge.result.validations[description] = Validation.new(result: :passed)
|
25
|
+
rescue StandardError, RSpec::Expectations::ExpectationNotMetError => e
|
26
|
+
validation = Validation.new(result: :failed, error: ValidationFailure.new(e.message, e))
|
27
|
+
challenge.result.validations[description] = validation
|
23
28
|
end
|
24
29
|
|
25
30
|
def to_s
|
data/lib/polytrix/version.rb
CHANGED
data/lib/polytrix.rb
CHANGED
@@ -1,140 +1,162 @@
|
|
1
|
+
require 'thor'
|
1
2
|
require 'pathname'
|
2
|
-
require 'polytrix/error'
|
3
|
-
require 'polytrix/core/hashie'
|
4
|
-
require 'polytrix/core/string_helpers'
|
5
3
|
require 'polytrix/version'
|
6
|
-
require 'polytrix/util'
|
7
|
-
require 'polytrix/color'
|
8
4
|
require 'polytrix/logger'
|
9
5
|
require 'polytrix/logging'
|
6
|
+
require 'polytrix/error'
|
7
|
+
require 'polytrix/dash'
|
8
|
+
require 'polytrix/util'
|
9
|
+
require 'polytrix/color'
|
10
10
|
require 'polytrix/state_file'
|
11
|
-
require 'polytrix/
|
12
|
-
|
13
|
-
require 'polytrix/
|
14
|
-
require 'polytrix/core/implementor'
|
11
|
+
require 'polytrix/spies'
|
12
|
+
# TODO: Merge these two classes?
|
13
|
+
require 'polytrix/executor'
|
15
14
|
require 'polytrix/challenge_runner'
|
15
|
+
require 'polytrix/implementor'
|
16
16
|
require 'polytrix/challenge_result'
|
17
17
|
require 'polytrix/challenge'
|
18
18
|
require 'polytrix/challenges'
|
19
19
|
require 'polytrix/manifest'
|
20
20
|
require 'polytrix/configuration'
|
21
21
|
require 'polytrix/validation'
|
22
|
-
require 'polytrix/validations'
|
23
22
|
require 'polytrix/result'
|
24
23
|
require 'polytrix/documentation_generator'
|
25
24
|
require 'polytrix/validator'
|
26
25
|
require 'polytrix/validator_registry'
|
27
26
|
|
28
|
-
require 'polytrix/rspec'
|
29
|
-
|
30
27
|
module Polytrix
|
31
28
|
include Polytrix::DefaultLogger
|
32
29
|
include Polytrix::Logging
|
33
30
|
|
31
|
+
# File extensions that Polytrix can automatically detect/execute
|
32
|
+
SUPPORTED_EXTENSIONS = %w(py rb js)
|
33
|
+
|
34
34
|
class << self
|
35
|
-
include Polytrix::
|
35
|
+
include Polytrix::Util::FileSystem
|
36
|
+
|
37
|
+
DEFAULT_MANIFEST_FILE = 'polytrix.yml'
|
36
38
|
|
37
39
|
# @return [Mutex] a common mutex for global coordination
|
38
40
|
attr_accessor :mutex
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
# @return [Logger] the common Polytrix logger
|
43
|
+
attr_accessor :logger
|
44
|
+
|
45
|
+
def setup(options, manifest_file = DEFAULT_MANIFEST_FILE)
|
46
|
+
# manifest_file = File.expand_path manifest
|
47
|
+
if File.exist? manifest_file
|
48
|
+
logger.debug "Loading manifest file: #{manifest_file}"
|
49
|
+
Polytrix.configuration.manifest = manifest_file
|
50
|
+
elsif options[:solo]
|
51
|
+
solo_setup(options)
|
52
|
+
else
|
53
|
+
fail StandardError, "No manifest found at #{manifest_file} and not using --solo mode"
|
54
|
+
end
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
configuration.manifest
|
48
|
-
end
|
56
|
+
Polytrix.configuration.documentation_dir = options[:target_dir]
|
57
|
+
Polytrix.configuration.documentation_format = options[:format]
|
49
58
|
|
50
|
-
|
51
|
-
def implementors
|
52
|
-
manifest.implementors.values
|
53
|
-
end
|
59
|
+
manifest.build_challenges
|
54
60
|
|
55
|
-
|
56
|
-
|
57
|
-
implementors.find do |implementor|
|
58
|
-
File.absolute_path(implementor.basedir) == File.absolute_path(path)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
return existing_implementor if existing_implementor
|
61
|
+
test_dir = options[:test_dir].nil? ? nil : File.expand_path(options[:test_dir])
|
62
|
+
return nil unless test_dir && File.directory?(test_dir)
|
62
63
|
|
63
|
-
|
64
|
+
$LOAD_PATH.unshift test_dir
|
65
|
+
Dir["#{test_dir}/**/*.rb"].each do | file_to_require |
|
66
|
+
require relativize(file_to_require, test_dir).to_s.gsub('.rb', '')
|
67
|
+
end
|
64
68
|
end
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
def solo_setup(options)
|
71
|
+
suites = {}
|
72
|
+
solo_basedir = options[:solo]
|
73
|
+
solo_glob = options.fetch('solo_glob', "**/*.{#{SUPPORTED_EXTENSIONS.join(',')}}")
|
74
|
+
Dir[File.join(solo_basedir, solo_glob)].sort.each do | code_sample |
|
75
|
+
code_sample = Pathname.new(code_sample)
|
76
|
+
suite_name = relativize(code_sample.dirname, solo_basedir).to_s
|
77
|
+
suite_name = solo_basedir if suite_name == '.'
|
78
|
+
scenario_name = code_sample.basename(code_sample.extname).to_s
|
79
|
+
suite = suites[suite_name] ||= Polytrix::Manifest::Suite.new(samples: [])
|
80
|
+
suite.samples << scenario_name
|
71
81
|
end
|
82
|
+
@manifest = Polytrix.configuration.manifest = Polytrix::Manifest.new(
|
83
|
+
implementors: {
|
84
|
+
File.basename(solo_basedir) => {
|
85
|
+
basedir: solo_basedir
|
86
|
+
}
|
87
|
+
},
|
88
|
+
suites: suites
|
89
|
+
)
|
72
90
|
end
|
73
91
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
92
|
+
def select_scenarios(regexp)
|
93
|
+
regexp ||= 'all'
|
94
|
+
scenarios = if regexp == 'all'
|
95
|
+
manifest.challenges.values
|
96
|
+
else
|
97
|
+
manifest.challenges.get(regexp) || manifest.challenges.get_all(/#{regexp}/)
|
98
|
+
end
|
99
|
+
if scenarios.is_a? Array
|
100
|
+
scenarios
|
101
|
+
else
|
102
|
+
[scenarios]
|
79
103
|
end
|
80
104
|
end
|
81
105
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
files.each do | file |
|
88
|
-
implementor = find_implementor(file) # || exec_options[:default_implementor]
|
89
|
-
|
90
|
-
extension = File.extname(file)
|
91
|
-
name = File.basename(file, extension)
|
92
|
-
challenge_data = {
|
93
|
-
name: name,
|
94
|
-
# language: extension,
|
95
|
-
source_file: File.expand_path(file, Dir.pwd)
|
96
|
-
}
|
97
|
-
challenge = implementor.build_challenge challenge_data
|
98
|
-
challenge.exec
|
106
|
+
def filter_scenarios(regexp, options = {})
|
107
|
+
select_scenarios(regexp).tap do |scenarios|
|
108
|
+
scenarios.keep_if { |scenario| scenario.failed? == options[:failed] } unless options[:failed].nil?
|
109
|
+
scenarios.keep_if { |scenario| scenario.skipped? == options[:skipped] } unless options[:skipped].nil?
|
110
|
+
scenarios.keep_if { |scenario| scenario.sample? == options[:samples] } unless options[:samples].nil?
|
99
111
|
end
|
100
112
|
end
|
101
113
|
|
102
|
-
#
|
103
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
114
|
+
# Returns a default file logger which emits on standard output and to a
|
115
|
+
# log file.
|
116
|
+
#
|
117
|
+
# @return [Logger] a logger
|
118
|
+
def default_file_logger
|
119
|
+
logfile = File.expand_path(File.join('.polytrix', 'logs', 'polytrix.log'))
|
120
|
+
Logger.new(stdout: $stdout, logdev: logfile, level: env_log)
|
121
|
+
end
|
107
122
|
|
108
|
-
|
109
|
-
|
123
|
+
# Determine the default log level from an environment variable, if it is
|
124
|
+
# set.
|
125
|
+
#
|
126
|
+
# @return [Integer,nil] a log level or nil if not set
|
127
|
+
# @api private
|
128
|
+
def env_log
|
129
|
+
level = ENV['POLYTRIX_LOG'] && ENV['POLYTRIX_LOG'].downcase.to_sym
|
130
|
+
level = Util.to_logger_level(level) unless level.nil?
|
131
|
+
level
|
110
132
|
end
|
111
133
|
|
112
|
-
|
113
|
-
|
114
|
-
|
134
|
+
# Default log level verbosity
|
135
|
+
DEFAULT_LOG_LEVEL = :info
|
136
|
+
|
137
|
+
def reset
|
138
|
+
@configuration = nil
|
139
|
+
Polytrix::ValidatorRegistry.clear
|
115
140
|
end
|
116
141
|
|
117
|
-
#
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
rspec_options.concat Polytrix.configuration.rspec_options.split if Polytrix.configuration.rspec_options
|
122
|
-
unless implementors.empty?
|
123
|
-
target_sdks = implementors.map(&:name)
|
124
|
-
Polytrix.implementors.map(&:name).each do |sdk|
|
125
|
-
# We don't have an "or" for tags, so it's easier to exclude than include multiple tags
|
126
|
-
rspec_options.concat %W[-t ~#{sdk.to_sym}] unless target_sdks.include? sdk
|
127
|
-
end
|
128
|
-
end
|
142
|
+
# The {Polytrix::Manifest} that describes the test scenarios known to Polytrix.
|
143
|
+
def manifest
|
144
|
+
configuration.manifest
|
145
|
+
end
|
129
146
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
logger.info "polytrix:test\tTest execution completed"
|
147
|
+
# The set of {Polytrix::Implementor}s registered with Polytrix.
|
148
|
+
def implementors
|
149
|
+
manifest.implementors.values
|
134
150
|
end
|
135
151
|
|
136
|
-
|
137
|
-
|
152
|
+
# Registers a {Polytrix::Validator} that will be used during test
|
153
|
+
# execution on matching {Polytrix::Challenge}s.
|
154
|
+
def validate(desc, scope = { suite: //, scenario: // }, &block)
|
155
|
+
fail ArgumentError 'You must pass block' unless block_given?
|
156
|
+
validator = Polytrix::Validator.new(desc, scope, &block)
|
157
|
+
|
158
|
+
Polytrix::ValidatorRegistry.register validator
|
159
|
+
validator
|
138
160
|
end
|
139
161
|
|
140
162
|
# @see Polytrix::Configuration
|
@@ -155,23 +177,6 @@ module Polytrix
|
|
155
177
|
def tty?
|
156
178
|
$stdout.tty?
|
157
179
|
end
|
158
|
-
|
159
|
-
protected
|
160
|
-
|
161
|
-
def select_implementors(sdks)
|
162
|
-
return implementors if sdks.empty?
|
163
|
-
|
164
|
-
sdks.map do |sdk|
|
165
|
-
if File.directory? sdk
|
166
|
-
sdk_dir = File.absolute_path(sdk)
|
167
|
-
implementors.find { |i| File.absolute_path(i.basedir) == sdk_dir } || configuration.implementor(sdk_dir)
|
168
|
-
else
|
169
|
-
implementor = implementors.find { |i| i.name.to_s.downcase == sdk.to_s.downcase }
|
170
|
-
fail ArgumentError, "SDK #{sdk} not found" if implementor.nil?
|
171
|
-
implementor
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
180
|
end
|
176
181
|
end
|
177
182
|
|
data/polytrix.gemspec
CHANGED
@@ -23,12 +23,17 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency "mixlib-shellout", "~> 1.3" # Used for MRI
|
24
24
|
spec.add_dependency "buff-shell_out", "~> 0.1" # Used for JRuby
|
25
25
|
spec.add_dependency "middleware", "~> 0.1"
|
26
|
-
spec.add_dependency "rspec", "~> 3.0"
|
26
|
+
spec.add_dependency "rspec-expectations", "~> 3.0"
|
27
27
|
spec.add_dependency "hashie", "~> 3.0"
|
28
28
|
spec.add_dependency "padrino-helpers", "~> 0.12"
|
29
|
+
spec.add_dependency "erubis", "~> 2.7"
|
30
|
+
spec.add_dependency "cause", "~> 0.1"
|
31
|
+
spec.add_dependency "rouge", "~> 1.7"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
33
|
spec.add_development_dependency "bundler", "~> 1.5"
|
30
34
|
spec.add_development_dependency "rake"
|
31
35
|
spec.add_development_dependency "aruba", "~> 0.5"
|
32
|
-
spec.add_development_dependency 'rubocop', '~> 0.18
|
36
|
+
spec.add_development_dependency 'rubocop', '~> 0.18'
|
37
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.2'
|
33
38
|
spec.add_development_dependency 'fabrication', '~> 2.11'
|
34
39
|
end
|