polytrix 0.1.2 → 0.1.3

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-todo.yml +14 -5
  3. data/Gemfile +2 -1
  4. data/README.md +139 -177
  5. data/Rakefile +5 -12
  6. data/bin/polytrix +0 -1
  7. data/features/bootstrapping.feature +0 -3
  8. data/features/cloning.feature +0 -3
  9. data/features/show.feature +38 -0
  10. data/features/states.feature +12 -13
  11. data/features/step_definitions/sdk_steps.rb +0 -4
  12. data/lib/polytrix/challenge.rb +135 -53
  13. data/lib/polytrix/challenge_result.rb +0 -2
  14. data/lib/polytrix/challenge_runner.rb +28 -18
  15. data/lib/polytrix/cli.rb +53 -69
  16. data/lib/polytrix/color.rb +2 -2
  17. data/lib/polytrix/command/action.rb +4 -3
  18. data/lib/polytrix/command/list.rb +39 -28
  19. data/lib/polytrix/command/report.rb +9 -86
  20. data/lib/polytrix/command/reports/code2doc.rb +72 -0
  21. data/lib/polytrix/command/reports/dashboard.rb +125 -0
  22. data/lib/polytrix/command/show.rb +148 -0
  23. data/lib/polytrix/command.rb +37 -104
  24. data/lib/polytrix/configuration.rb +14 -18
  25. data/lib/polytrix/{core/hashie.rb → dash.rb} +4 -3
  26. data/lib/polytrix/documentation/code_segmenter.rb +8 -8
  27. data/lib/polytrix/documentation/comment_styles.rb +1 -1
  28. data/lib/polytrix/documentation/helpers/code_helper.rb +9 -0
  29. data/lib/polytrix/documentation_generator.rb +11 -14
  30. data/lib/polytrix/error.rb +104 -97
  31. data/lib/polytrix/executor.rb +33 -0
  32. data/lib/polytrix/{runners → executors}/buff_shellout_executor.rb +1 -1
  33. data/lib/polytrix/executors/linux_challenge_executor.rb +29 -0
  34. data/lib/polytrix/executors/mixlib_shellout_executor.rb +55 -0
  35. data/lib/polytrix/{runners/windows_challenge_runner.rb → executors/windows_challenge_executor.rb} +4 -11
  36. data/lib/polytrix/{core/implementor.rb → implementor.rb} +10 -6
  37. data/lib/polytrix/manifest.rb +2 -31
  38. data/lib/polytrix/{reports → reporters}/hash_reporter.rb +6 -2
  39. data/lib/polytrix/{reports → reporters}/json_reporter.rb +2 -2
  40. data/lib/polytrix/{reports → reporters}/markdown_reporter.rb +7 -2
  41. data/lib/polytrix/{reports → reporters}/yaml_reporter.rb +2 -2
  42. data/lib/polytrix/reporters.rb +27 -0
  43. data/lib/polytrix/result.rb +6 -5
  44. data/lib/polytrix/spies/file_system_spy.rb +15 -0
  45. data/lib/polytrix/spies.rb +61 -0
  46. data/lib/polytrix/state_file.rb +1 -20
  47. data/lib/polytrix/util.rb +157 -62
  48. data/lib/polytrix/validation.rb +41 -2
  49. data/lib/polytrix/validator.rb +9 -4
  50. data/lib/polytrix/version.rb +1 -1
  51. data/lib/polytrix.rb +110 -105
  52. data/polytrix.gemspec +7 -2
  53. data/polytrix.yml +16 -13
  54. data/resources/assets/pygments/autumn.css +58 -0
  55. data/resources/assets/pygments/borland.css +46 -0
  56. data/resources/assets/pygments/bw.css +34 -0
  57. data/resources/assets/pygments/colorful.css +61 -0
  58. data/resources/assets/pygments/default.css +62 -0
  59. data/resources/assets/pygments/emacs.css +61 -0
  60. data/resources/assets/pygments/friendly.css +61 -0
  61. data/resources/assets/pygments/fruity.css +69 -0
  62. data/resources/assets/pygments/github.css +61 -0
  63. data/resources/assets/pygments/manni.css +61 -0
  64. data/resources/assets/pygments/monokai.css +64 -0
  65. data/resources/assets/pygments/murphy.css +61 -0
  66. data/resources/assets/pygments/native.css +69 -0
  67. data/resources/assets/pygments/pastie.css +60 -0
  68. data/resources/assets/pygments/perldoc.css +58 -0
  69. data/resources/assets/pygments/tango.css +69 -0
  70. data/resources/assets/pygments/trac.css +59 -0
  71. data/resources/assets/pygments/vim.css +69 -0
  72. data/resources/assets/pygments/vs.css +33 -0
  73. data/resources/assets/pygments/zenburn.css +1 -0
  74. data/resources/assets/style.css +41 -0
  75. data/resources/templates/dashboard/files/dashboard.html.tt +82 -0
  76. data/resources/templates/dashboard/templates/_test_report.html.tt +87 -0
  77. data/samples/bootstrap.sh +2 -0
  78. data/samples/clone.sh +2 -0
  79. data/samples/code2doc.sh +4 -0
  80. data/samples/docs/samples/code2doc/java/katas-hello_world-java.md +17 -0
  81. data/samples/docs/samples/code2doc/java/katas-quine-java.md +35 -0
  82. data/samples/docs/samples/code2doc/python/katas-hello_world-python.md +5 -0
  83. data/samples/docs/samples/code2doc/python/katas-quine-python.md +6 -0
  84. data/samples/docs/samples/code2doc/ruby/katas-hello_world-ruby.md +11 -0
  85. data/samples/exec.sh +2 -0
  86. data/samples/polytrix.rb +2 -2
  87. data/samples/polytrix.yml +5 -2
  88. data/samples/show.sh +4 -0
  89. data/samples/test.sh +2 -0
  90. data/samples/tests/polytrix/validators.rb +2 -2
  91. data/samples/verify.sh +3 -0
  92. data/scripts/wrapper +4 -7
  93. data/spec/fabricators/challenge_fabricator.rb +2 -9
  94. data/spec/fabricators/implementor_fabricator.rb +0 -8
  95. data/spec/fabricators/manifest_fabricator.rb +2 -9
  96. data/spec/fabricators/validator_fabricator.rb +2 -4
  97. data/spec/polytrix/challenge_runner_spec.rb +20 -0
  98. data/spec/polytrix/documentation/helpers/code_helper_spec.rb +7 -7
  99. data/spec/polytrix/file_finder_spec.rb +5 -5
  100. data/spec/polytrix/manifest_spec.rb +0 -21
  101. data/spec/polytrix/result_spec.rb +14 -14
  102. data/spec/polytrix/validator_registry_spec.rb +4 -4
  103. data/spec/polytrix/validator_spec.rb +9 -9
  104. data/spec/polytrix_spec.rb +1 -25
  105. data/spec/spec_helper.rb +8 -1
  106. metadata +130 -38
  107. data/features/execution.feature +0 -53
  108. data/features/fixtures/spec/polytrix_spec.rb +0 -7
  109. data/lib/polytrix/cli/report.rb +0 -84
  110. data/lib/polytrix/command/rundoc.rb +0 -27
  111. data/lib/polytrix/core/file_system_helper.rb +0 -75
  112. data/lib/polytrix/core/manifest_section.rb +0 -4
  113. data/lib/polytrix/core/string_helpers.rb +0 -15
  114. data/lib/polytrix/documentation/view_helper.rb +0 -21
  115. data/lib/polytrix/rspec/documentation_formatter.rb +0 -66
  116. data/lib/polytrix/rspec/yaml_report.rb +0 -51
  117. data/lib/polytrix/rspec.rb +0 -56
  118. data/lib/polytrix/runners/executor.rb +0 -34
  119. data/lib/polytrix/runners/linux_challenge_runner.rb +0 -23
  120. data/lib/polytrix/runners/middleware/change_directory.rb +0 -20
  121. data/lib/polytrix/runners/middleware/feature_executor.rb +0 -24
  122. data/lib/polytrix/runners/middleware/setup_env_vars.rb +0 -42
  123. data/lib/polytrix/runners/mixlib_shellout_executor.rb +0 -83
  124. data/lib/polytrix/validations.rb +0 -23
  125. data/samples/scripts/wrapper +0 -7
  126. data/spec/polytrix/middleware/feature_executor_spec.rb +0 -48
  127. 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
- # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
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.reduce({}) do |h, (k, v)|
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.reduce([]) do |a, e|
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.reduce({}) do |h, (k, v)|
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.reduce([]) do |a, e|
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
- # Generates a command (or series of commands) wrapped so that it can be
111
- # invoked on a remote instance or locally.
112
- #
113
- # This method uses the Bourne shell (/bin/sh) to maximize the chance of
114
- # cross platform portability on Unixlike systems.
115
- #
116
- # @param [String] the command
117
- # @return [String] a wrapped command string
118
- def self.wrap_command(cmd)
119
- cmd = 'false' if cmd.nil?
120
- cmd = 'true' if cmd.to_s.empty?
121
- cmd = cmd.sub(/\n\Z/, '') if cmd =~ /\n\Z/
122
-
123
- "sh -c '\n#{cmd}\n'"
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
- # Modifes the given string to strip leading whitespace on each line, the
127
- # amount which is calculated by using the first line of text.
128
- #
129
- # @example
130
- #
131
- # string = <<-STRING
132
- # a
133
- # b
134
- # c
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
- # Returns a set of Bourne Shell (AKA /bin/sh) compatible helper
145
- # functions. This function is usually called inline in a string that
146
- # will be executed remotely on a test instance.
147
- #
148
- # @return [String] a string representation of useful helper functions
149
- def self.shell_helpers
150
- IO.read(File.join(
151
- File.dirname(__FILE__), %w[.. .. support download_helpers.sh]
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
+ '&' => '&amp;',
180
+ '<' => '&lt;',
181
+ '>' => '&gt;',
182
+ "'" => '&#x27;',
183
+ '"' => '&quot;',
184
+ '/' => '&#x2F;'
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
@@ -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
- property :validated_by, required: true
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
@@ -5,21 +5,26 @@ module Polytrix
5
5
  include RSpec::Matchers
6
6
 
7
7
  UNIVERSAL_MATCHER = //
8
- attr_reader :description, :suite, :sample, :level, :callback
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
- @sample = scope[:sample] ||= UNIVERSAL_MATCHER
13
+ @scenario = scope[:scenario] ||= UNIVERSAL_MATCHER
14
14
  @callback = validator
15
15
  end
16
16
 
17
17
  def should_validate?(challenge)
18
- !!(@suite.match(challenge.suite.to_s) && @sample.match(challenge.name.to_s))
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 challenge, &@callback if should_validate?(challenge)
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
@@ -1,3 +1,3 @@
1
1
  module Polytrix
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end
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/core/file_system_helper'
12
- require 'polytrix/runners/executor'
13
- require 'polytrix/core/manifest_section'
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::Core::FileSystemHelper
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
- def reset
41
- @configuration = nil
42
- Polytrix::ValidatorRegistry.clear
43
- end
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
- # The {Polytrix::Manifest} that describes the test scenarios known to Polytrix.
46
- def manifest
47
- configuration.manifest
48
- end
56
+ Polytrix.configuration.documentation_dir = options[:target_dir]
57
+ Polytrix.configuration.documentation_format = options[:format]
49
58
 
50
- # The set of {Polytrix::Implementor}s registered with Polytrix.
51
- def implementors
52
- manifest.implementors.values
53
- end
59
+ manifest.build_challenges
54
60
 
55
- def find_implementor(file)
56
- existing_implementor = recursive_parent_search(File.dirname(file)) do |path|
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
- nil
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
- # Invokes the clone action for each SDK.
67
- # @see Polytrix::Implementor#clone
68
- def clone(*sdks)
69
- select_implementors(sdks).each do |implementor|
70
- implementor.clone
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
- # Invokes the bootstrap action for each SDK.
75
- # @see Polytrix::Implementor#bootstrap
76
- def bootstrap(*sdks)
77
- select_implementors(sdks).each do |implementor|
78
- implementor.bootstrap
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 exec(*files)
83
- # files.map do | file |
84
- # Dir.glob file
85
- # end.flatten
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
- # Registers a {Polytrix::Validator} that will be used during test
103
- # execution on matching {Polytrix::Challenge}s.
104
- def validate(desc, scope = { suite: //, sample: // }, &block)
105
- fail ArgumentError 'You must pass block' unless block_given?
106
- validator = Polytrix::Validator.new(desc, scope, &block)
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
- Polytrix::ValidatorRegistry.register validator
109
- validator
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
- def load_tests
113
- manifest.build_challenges
114
- Polytrix::RSpec.run_manifest(manifest)
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
- # Runs all of the tests described in the {manifest}
118
- def verify(implementors = [])
119
- test_env = ENV['TEST_ENV_NUMBER'].to_i
120
- rspec_options = %W[--color -f documentation -f Polytrix::RSpec::YAMLReport -o reports/test_report#{test_env}.yaml]
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
- load_tests
131
- logger.info "polytrix:test\tTesting with rspec options: #{rspec_options.join ' '}"
132
- ::RSpec::Core::Runner.run rspec_options
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
- def reset
137
- @configuration = nil
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.0'
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