polytrix 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -2,13 +2,10 @@ require 'thread'
2
2
 
3
3
  module Polytrix
4
4
  module Command
5
- class Base
5
+ class Base # rubocop:disable ClassLength
6
6
  include Polytrix::DefaultLogger
7
7
  include Polytrix::Logging
8
- include Polytrix::Core::FileSystemHelper
9
-
10
- # Need standard executor...
11
- SUPPORTED_EXTENSIONS = %w(py rb js)
8
+ include Polytrix::Util::FileSystem
12
9
 
13
10
  # Contstructs a new Command object.
14
11
  #
@@ -28,9 +25,9 @@ module Polytrix
28
25
  @action = options.fetch(:action, nil)
29
26
  @help = options.fetch(:help, -> { 'No help provided' })
30
27
  @manifest_file = options.fetch('manifest', nil)
31
- @test_dir = options.fetch('test_dir', nil)
32
28
  @loader = options.fetch(:loader, nil)
33
29
  @shell = options.fetch(:shell)
30
+ @queue = Queue.new
34
31
  end
35
32
 
36
33
  private
@@ -47,10 +44,6 @@ module Polytrix
47
44
  # @api private
48
45
  attr_reader :help
49
46
 
50
- # @return [Config] a Config object
51
- # @api private
52
- attr_reader :test_dir
53
-
54
47
  # @return [Thor::Shell] a Thor shell object
55
48
  # @api private
56
49
  attr_reader :shell
@@ -60,50 +53,7 @@ module Polytrix
60
53
  attr_reader :action
61
54
 
62
55
  def setup
63
- manifest_file = File.expand_path @manifest_file
64
- if File.exists? manifest_file
65
- logger.debug "Loading manifest file: #{manifest_file}"
66
- Polytrix.configuration.manifest = @manifest_file
67
- elsif @options.solo
68
- solo_setup
69
- else
70
- fail StandardError, "No manifest found at #{manifest_file} and not using --solo mode"
71
- end
72
-
73
- Polytrix.configuration.documentation_dir = options[:target_dir]
74
- Polytrix.configuration.documentation_format = options[:format]
75
-
76
- manifest.build_challenges
77
-
78
- test_dir = @test_dir.nil? ? nil : File.expand_path(@test_dir)
79
- if test_dir && File.directory?(test_dir)
80
- $LOAD_PATH.unshift test_dir
81
- Dir["#{test_dir}/**/*.rb"].each do | file_to_require |
82
- require relativize(file_to_require, test_dir).to_s.gsub('.rb', '')
83
- end
84
- end
85
- end
86
-
87
- def solo_setup
88
- suites = {}
89
- solo_basedir = @options.solo
90
- solo_glob = @options.fetch('solo_glob', "**/*.{#{SUPPORTED_EXTENSIONS.join(',')}}")
91
- Dir[File.join(solo_basedir, solo_glob)].sort.each do | code_sample |
92
- code_sample = Pathname.new(code_sample)
93
- suite_name = relativize(code_sample.dirname, solo_basedir).to_s
94
- suite_name = solo_basedir if suite_name == '.'
95
- scenario_name = code_sample.basename(code_sample.extname).to_s
96
- suite = suites[suite_name] ||= Polytrix::Manifest::Suite.new(samples: [])
97
- suite.samples << scenario_name
98
- end
99
- @manifest = Polytrix.configuration.manifest = Polytrix::Manifest.new(
100
- implementors: {
101
- File.basename(solo_basedir) => {
102
- basedir: solo_basedir
103
- }
104
- },
105
- suites: suites
106
- )
56
+ Polytrix.setup(options, @manifest_file)
107
57
  end
108
58
 
109
59
  def manifest
@@ -124,44 +74,6 @@ module Polytrix
124
74
  exit 1
125
75
  end
126
76
 
127
- # @return [Array<Scenario>] an array of scenarios
128
- # @raise [SystemExit] if no scenario are returned
129
- # @api private
130
- def all_scenarios
131
- result = manifest.challenges.values
132
-
133
- if result.empty?
134
- die 'No scenarios defined'
135
- else
136
- result
137
- end
138
- end
139
-
140
- # Return an array on scenarios whos name matches the regular expression.
141
- #
142
- # @param regexp [Regexp] a regular expression matching on instance names
143
- # @return [Array<Instance>] an array of scenarios
144
- # @raise [SystemExit] if no scenarios are returned or the regular
145
- # expression is invalid
146
- # @api private
147
- def filtered_scenarios(regexp)
148
- result = begin
149
- manifest.challenges.get(regexp) ||
150
- manifest.challenges.get_all(/#{regexp}/)
151
- rescue RegexpError => e
152
- die "Invalid Ruby regular expression, " \
153
- "you may need to single quote the argument. " \
154
- "Please try again or consult http://rubular.com/ (#{e.message})"
155
- end
156
- result = [result] unless result.is_a? Array
157
-
158
- if result.empty?
159
- die "No scenarios for regex `#{regexp}', try running `polytrix list'"
160
- else
161
- result
162
- end
163
- end
164
-
165
77
  # Return an array on scenarios whos name matches the regular expression,
166
78
  # the full instance name, or the `"all"` literal.
167
79
  #
@@ -169,9 +81,14 @@ module Polytrix
169
81
  # `"all"`, or `nil`
170
82
  # @return [Array<Instance>] an array of scenarios
171
83
  # @api private
172
- def parse_subcommand(arg = nil)
173
- arg ||= 'all'
174
- arg == 'all' ? all_scenarios : filtered_scenarios(arg)
84
+ def parse_subcommand(regexp = nil)
85
+ scenarios = Polytrix.filter_scenarios(regexp, options)
86
+ die "No scenarios for regex `#{regexp}', try running `polytrix list'" if scenarios.empty?
87
+ scenarios
88
+ rescue RegexpError => e
89
+ die 'Invalid Ruby regular expression, ' \
90
+ 'you may need to single quote the argument. ' \
91
+ "Please try again or consult http://rubular.com/ (#{e.message})"
175
92
  end
176
93
  end
177
94
 
@@ -184,26 +101,42 @@ module Polytrix
184
101
  #
185
102
  # @param action [String] action to perform
186
103
  # @param scenarios [Array<Instance>] an array of scenarios
187
- def run_action(action, scenarios, *args)
104
+ def run_action(_action, scenarios, *_args)
188
105
  concurrency = 1
189
106
  if options[:concurrency]
190
107
  concurrency = options[:concurrency] || scenarios.size
191
108
  concurrency = scenarios.size if concurrency > scenarios.size
192
109
  end
193
110
 
194
- queue = Queue.new
195
- scenarios.each { |i| queue << i }
196
- concurrency.times { queue << nil }
111
+ scenarios.each { |i| @queue << i }
112
+ concurrency.times { @queue << nil }
113
+
114
+ threads = concurrency.times.map { |i| spawn(i) }
115
+ threads.map do |t|
116
+ begin
117
+ t.join
118
+ rescue Polytrix::ExecutionError, Polytrix::ChallengeFailure
119
+ # respawn thread
120
+ t.kill
121
+ threads.delete(t)
122
+ threads.push(spawn)
123
+ end
124
+ end while threads.any?(&:alive?)
125
+ end
126
+
127
+ private
197
128
 
198
- threads = []
199
- concurrency.times do
200
- threads << Thread.new do
201
- while (instance = queue.pop)
129
+ def spawn(i)
130
+ Thread.new(i) do |test_env_number|
131
+ Thread.current[:test_env_number] = test_env_number
132
+ while (instance = @queue.pop)
133
+ begin
202
134
  instance.public_send(action, *args)
135
+ rescue Polytrix::ExecutionError, Polytrix::ChallengeFailure => e
136
+ logger.error(e)
203
137
  end
204
138
  end
205
139
  end
206
- threads.map { |i| i.join }
207
140
  end
208
141
  end
209
142
  end
@@ -1,27 +1,14 @@
1
- require 'middleware'
1
+
2
+ require 'rspec/support'
3
+ require 'rspec/expectations'
2
4
 
3
5
  module Polytrix
4
6
  RESOURCES_DIR = File.expand_path '../../../resources', __FILE__
5
- # Autoload pool
6
- module Runners
7
- module Middleware
8
- autoload :FeatureExecutor, 'polytrix/runners/middleware/feature_executor'
9
- autoload :SetupEnvVars, 'polytrix/runners/middleware/setup_env_vars'
10
- autoload :ChangeDirectory, 'polytrix/runners/middleware/change_directory'
11
-
12
- STANDARD_MIDDLEWARE = ::Middleware::Builder.new do
13
- use Polytrix::Runners::Middleware::ChangeDirectory
14
- use Polytrix::Runners::Middleware::SetupEnvVars
15
- use Polytrix::Runners::Middleware::FeatureExecutor
16
- end
17
- end
18
- end
19
7
 
20
8
  class Configuration < Polytrix::ManifestSection
21
9
  property :dry_run, default: false
22
10
  property :log_root, default: '.polytrix/logs'
23
11
  property :log_level, default: :info
24
- property :middleware, default: Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
25
12
  property :implementors, default: []
26
13
  # coerce_key :implementors, Polytrix::Implementor
27
14
  property :suppress_output, default: false
@@ -29,8 +16,13 @@ module Polytrix
29
16
  property :template_dir, default: "#{RESOURCES_DIR}"
30
17
  property :documentation_dir, default: 'docs/'
31
18
  property :documentation_format, default: 'md'
32
- # Extra options for rspec
33
- property :rspec_options, default: ''
19
+
20
+ # TODO: This should probably be configurable, or tied to Thor color options.
21
+ if RSpec.respond_to?(:configuration)
22
+ RSpec.configuration.color = true
23
+ else
24
+ RSpec::Expectations.configuration.color = true
25
+ end
34
26
 
35
27
  def default_logger
36
28
  @default_logger ||= Logger.new(stdout: $stdout, level: env_log)
@@ -66,6 +58,10 @@ module Polytrix
66
58
 
67
59
  attr_writer :default_validator_callback
68
60
 
61
+ def register_spy(spy)
62
+ Polytrix::Spies.register_spy(spy)
63
+ end
64
+
69
65
  private
70
66
 
71
67
  # Determine the default log level from an environment variable, if it is
@@ -1,5 +1,4 @@
1
1
  require 'hashie/dash'
2
- require 'hashie/mash'
3
2
  require 'hashie/extensions/coercion'
4
3
 
5
4
  module Polytrix
@@ -7,8 +6,10 @@ module Polytrix
7
6
  include Hashie::Extensions::Coercion
8
7
 
9
8
  def initialize(hash = {})
10
- mash = Hashie::Mash.new(hash)
11
- super mash.to_hash(symbolize_keys: true)
9
+ super Polytrix::Util.symbolized_hash(hash)
12
10
  end
13
11
  end
12
+
13
+ class ManifestSection < Polytrix::Dash
14
+ end
14
15
  end
@@ -5,7 +5,7 @@ module Polytrix
5
5
  # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
6
6
  # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
7
7
  class CodeSegmenter # rubocop:disable all
8
- # Cops are disabled because the code is from Rocco
8
+ # Cops are disabled because the code is from Rocco
9
9
  include CommentStyles
10
10
 
11
11
  DEFAULT_OPTIONS = {
@@ -23,7 +23,7 @@ module Polytrix
23
23
  # Parse the raw file source_code into a list of two-tuples. Each tuple has the
24
24
  # form `[docs, code]` where both elements are arrays containing the
25
25
  # raw lines parsed from the input file, comment characters stripped.
26
- def segment(source_code)
26
+ def segment(source_code) # rubocop:disable all
27
27
  sections, docs, code = [], [], []
28
28
  lines = source_code.split("\n")
29
29
 
@@ -42,20 +42,20 @@ module Polytrix
42
42
  in_heredoc = false
43
43
  single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
44
44
  nil, nil, nil, nil
45
- if not @options[:comment_chars][:single].nil?
45
+ unless @options[:comment_chars][:single].nil?
46
46
  single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
47
47
  end
48
- if not @options[:comment_chars][:multi].nil?
48
+ unless @options[:comment_chars][:multi].nil?
49
49
  block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
50
50
  block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
51
- block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
51
+ block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$") # rubocop:disable Metrics/LineLength
52
52
  block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
53
53
  block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
54
54
  if @options[:comment_chars][:multi][:middle]
55
55
  block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
56
56
  end
57
57
  end
58
- if not @options[:comment_chars][:heredoc].nil?
58
+ unless @options[:comment_chars][:heredoc].nil?
59
59
  heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
60
60
  end
61
61
  lines.each do |line|
@@ -67,8 +67,8 @@ module Polytrix
67
67
  in_comment_block = false
68
68
  elsif block_comment_end_with && line.match(block_comment_end_with)
69
69
  in_comment_block = false
70
- docs << line.match(block_comment_end_with).captures.first.
71
- sub(block_comment_mid || '', '')
70
+ docs << line.match(block_comment_end_with).captures.first
71
+ .sub(block_comment_mid || '', '')
72
72
  else
73
73
  docs << line.sub(block_comment_mid || '', '')
74
74
  end
@@ -15,7 +15,7 @@ module Polytrix
15
15
  extension.tr! '.', ''
16
16
  return extension, COMMENT_STYLES[extension] if COMMENT_STYLES.key? extension
17
17
 
18
- COMMENT_STYLES.each do | style_name, style |
18
+ COMMENT_STYLES.each do | _style_name, style |
19
19
  return extension, style if style[:extensions].include? extension
20
20
  end
21
21
 
@@ -1,4 +1,5 @@
1
1
  require 'polytrix/documentation/code_segmenter'
2
+ require 'rouge'
2
3
 
3
4
  module Polytrix
4
5
  module Documentation
@@ -35,6 +36,14 @@ module Polytrix
35
36
  File.read absolute_source_file
36
37
  end
37
38
 
39
+ def source?
40
+ !absolute_source_file.nil?
41
+ end
42
+
43
+ def highlighted_code(formatter = 'terminal256')
44
+ highlight(source, language: implementor.language, filename: absolute_source_file, formatter: formatter)
45
+ end
46
+
38
47
  def code_block(source_code, language, opts = { format: :markdown })
39
48
  case opts[:format]
40
49
  when :rst
@@ -1,4 +1,5 @@
1
- require 'tilt' # seems to be a bug where padrino-helpers should require tilt
1
+ require 'tilt' # padrino-helpers wants you to pre-require tilt/erubis
2
+ require 'erubis'
2
3
  require 'padrino-helpers'
3
4
 
4
5
  module Polytrix
@@ -20,23 +21,19 @@ module Polytrix
20
21
  end
21
22
 
22
23
  def process(challenges)
24
+ return nil unless File.readable? @template_file
25
+
23
26
  @challenges = challenges
24
- if File.readable? @template_file
25
- # @template_file ||= find_file @search_path, scenario, ""
26
- erb = ERB.new File.read(@template_file)
27
- @result = erb.result(binding) || ''
28
- end
27
+ erb = ERB.new File.read(@template_file)
28
+ @result = erb.result(binding) || ''
29
29
  end
30
30
 
31
31
  def save(target_file)
32
- fail 'No results to write, please call process before save' if @result.nil?
33
- if @result.empty?
34
- # Warn: skip creating empty file
35
- else
36
- FileUtils.mkdir_p File.dirname(target_file)
37
- File.open(target_file, 'wb') do |f|
38
- f.write @result
39
- end
32
+ fail 'No results to write, please call process before save' if @result.nil? || @result.empty?
33
+
34
+ FileUtils.mkdir_p File.dirname(target_file)
35
+ File.open(target_file, 'wb') do |f|
36
+ f.write @result
40
37
  end
41
38
  end
42
39
 
@@ -96,114 +96,121 @@ module Polytrix
96
96
  # Exception class capturing what caused an challenge to die.
97
97
  class ChallengeFailure < TransientFailure; end
98
98
 
99
+ # Exception class capturing what caused a validation to fail.
100
+ class ValidationFailure < TransientFailure; end
101
+
99
102
  class ExecutionError < TransientFailure
100
103
  attr_accessor :execution_result
101
104
  end
102
105
 
103
- # Yields to a code block in order to consistently emit a useful crash/error
104
- # message and exit appropriately. There are two primary failure conditions:
105
- # an expected challenge failure, and any other unexpected failures.
106
- #
107
- # **Note** This method may call `Kernel.exit` so may not return if the
108
- # yielded code block raises an exception.
109
- #
110
- # ## Challenge Failure
111
- #
112
- # This is an expected failure scenario which could happen if an challenge
113
- # couldn't be created, a Chef run didn't successfully converge, a
114
- # post-convergence test suite failed, etc. In other words, you can count on
115
- # encountering these failures all the time--this is Polytrix's worldview:
116
- # crash early and often. In this case a cleanly formatted exception is
117
- # written to `STDERR` and the exception message is written to
118
- # the common Polytrix file logger.
119
- #
120
- # ## Unexpected Failure
121
- #
122
- # All other forms of `Polytrix::Error` exceptions are considered unexpected
123
- # or unplanned exceptions, typically from user configuration errors, driver
124
- # or provisioner coding issues or bugs, or internal code issues. Given
125
- # a stable release of Polytrix and a solid set of drivers and provisioners,
126
- # the most likely cause of this is user configuration error originating in
127
- # the `.polytrix.yml` setup. For this reason, the exception is written to
128
- # `STDERR`, a full formatted exception trace is written to the common
129
- # Polytrix file logger, and a message is displayed on `STDERR` to the user
130
- # informing them to check the log files and check their configuration with
131
- # the `polytrix diagnose` subcommand.
132
- #
133
- # @raise [SystemExit] if an exception is raised in the yielded block
134
- def self.with_friendly_errors
135
- yield
136
- rescue Polytrix::ChallengeFailure => e
137
- Polytrix.mutex.synchronize do
138
- handle_challenge_failure(e)
139
- end
140
- exit 10
141
- rescue Polytrix::Error => e
142
- Polytrix.mutex.synchronize do
143
- handle_error(e)
106
+ class << self
107
+ # Yields to a code block in order to consistently emit a useful crash/error
108
+ # message and exit appropriately. There are two primary failure conditions:
109
+ # an expected challenge failure, and any other unexpected failures.
110
+ #
111
+ # **Note** This method may call `Kernel.exit` so may not return if the
112
+ # yielded code block raises an exception.
113
+ #
114
+ # ## Challenge Failure
115
+ #
116
+ # This is an expected failure scenario which could happen if an challenge
117
+ # couldn't be created, a Chef run didn't successfully converge, a
118
+ # post-convergence test suite failed, etc. In other words, you can count on
119
+ # encountering these failures all the time--this is Polytrix's worldview:
120
+ # crash early and often. In this case a cleanly formatted exception is
121
+ # written to `STDERR` and the exception message is written to
122
+ # the common Polytrix file logger.
123
+ #
124
+ # ## Unexpected Failure
125
+ #
126
+ # All other forms of `Polytrix::Error` exceptions are considered unexpected
127
+ # or unplanned exceptions, typically from user configuration errors, driver
128
+ # or provisioner coding issues or bugs, or internal code issues. Given
129
+ # a stable release of Polytrix and a solid set of drivers and provisioners,
130
+ # the most likely cause of this is user configuration error originating in
131
+ # the `.polytrix.yml` setup. For this reason, the exception is written to
132
+ # `STDERR`, a full formatted exception trace is written to the common
133
+ # Polytrix file logger, and a message is displayed on `STDERR` to the user
134
+ # informing them to check the log files and check their configuration with
135
+ # the `polytrix diagnose` subcommand.
136
+ #
137
+ # @raise [SystemExit] if an exception is raised in the yielded block
138
+ def with_friendly_errors
139
+ yield
140
+ rescue Polytrix::ChallengeFailure => e
141
+ Polytrix.mutex.synchronize do
142
+ handle_challenge_failure(e)
143
+ end
144
+ exit 10
145
+ rescue Polytrix::Error => e
146
+ Polytrix.mutex.synchronize do
147
+ handle_error(e)
148
+ end
149
+ exit 20
144
150
  end
145
- exit 20
146
- end
147
151
 
148
- private
149
-
150
- # Writes an array of lines to the common Polytrix logger's file device at the
151
- # given severity level. If the Polytrix logger is set to debug severity, then
152
- # the array of lines will also be written to the console output.
153
- #
154
- # @param level [Symbol,String] the desired log level
155
- # @param lines [Array<String>] an array of strings to log
156
- # @api private
157
- def self.file_log(level, lines)
158
- Array(lines).each do |line|
159
- if Polytrix.logger.debug?
160
- Polytrix.logger.debug(line)
161
- else
162
- Polytrix.logger.logdev && Polytrix.logger.logdev.public_send(level, line)
163
- end
152
+ # Handles an challenge failure exception.
153
+ #
154
+ # @param e [StandardError] an exception to handle
155
+ # @see Polytrix.with_friendly_errors
156
+ # @api private
157
+ def handle_challenge_failure(e)
158
+ stderr_log(e.message.split(/\s{2,}/))
159
+ stderr_log(Error.formatted_exception(e.original))
160
+ file_log(:error, e.message.split(/\s{2,}/).first)
161
+ debug_log(Error.formatted_trace(e))
164
162
  end
165
- end
166
163
 
167
- # Writes an array of lines to the `STDERR` device.
168
- #
169
- # @param lines [Array<String>] an array of strings to log
170
- # @api private
171
- def self.stderr_log(lines)
172
- Array(lines).each do |line|
173
- $stderr.puts(Color.colorize(">>>>>> #{line}", :red))
164
+ alias_method :handle_validation_failure, :handle_challenge_failure
165
+
166
+ # Handles an unexpected failure exception.
167
+ #
168
+ # @param e [StandardError] an exception to handle
169
+ # @see Polytrix.with_friendly_errors
170
+ # @api private
171
+ def handle_error(e)
172
+ stderr_log(Error.formatted_exception(e))
173
+ stderr_log('Please see .polytrix/logs/polytrix.log for more details')
174
+ # stderr_log("Also try running `polytrix diagnose --all` for configuration\n")
175
+ file_log(:error, Error.formatted_trace(e))
174
176
  end
175
- end
176
177
 
177
- # Writes an array of lines to the common Polytrix debugger with debug
178
- # severity.
179
- #
180
- # @param lines [Array<String>] an array of strings to log
181
- # @api private
182
- def self.debug_log(lines)
183
- Array(lines).each { |line| Polytrix.logger.debug(line) }
184
- end
178
+ private
185
179
 
186
- # Handles an challenge failure exception.
187
- #
188
- # @param e [StandardError] an exception to handle
189
- # @see Polytrix.with_friendly_errors
190
- # @api private
191
- def self.handle_challenge_failure(e)
192
- stderr_log(e.message.split(/\s{2,}/))
193
- stderr_log(Error.formatted_exception(e.original))
194
- file_log(:error, e.message.split(/\s{2,}/).first)
195
- debug_log(Error.formatted_trace(e))
196
- end
180
+ # Writes an array of lines to the common Polytrix logger's file device at the
181
+ # given severity level. If the Polytrix logger is set to debug severity, then
182
+ # the array of lines will also be written to the console output.
183
+ #
184
+ # @param level [Symbol,String] the desired log level
185
+ # @param lines [Array<String>] an array of strings to log
186
+ # @api private
187
+ def file_log(level, lines)
188
+ Array(lines).each do |line|
189
+ if Polytrix.logger.debug?
190
+ Polytrix.logger.debug(line)
191
+ else
192
+ Polytrix.logger.logdev && Polytrix.logger.logdev.public_send(level, line)
193
+ end
194
+ end
195
+ end
197
196
 
198
- # Handles an unexpected failure exception.
199
- #
200
- # @param e [StandardError] an exception to handle
201
- # @see Polytrix.with_friendly_errors
202
- # @api private
203
- def self.handle_error(e)
204
- stderr_log(Error.formatted_exception(e))
205
- stderr_log('Please see .polytrix/logs/polytrix.log for more details')
206
- # stderr_log("Also try running `polytrix diagnose --all` for configuration\n")
207
- file_log(:error, Error.formatted_trace(e))
197
+ # Writes an array of lines to the `STDERR` device.
198
+ #
199
+ # @param lines [Array<String>] an array of strings to log
200
+ # @api private
201
+ def stderr_log(lines)
202
+ Array(lines).each do |line|
203
+ $stderr.puts(Color.colorize(">>>>>> #{line}", :red))
204
+ end
205
+ end
206
+
207
+ # Writes an array of lines to the common Polytrix debugger with debug
208
+ # severity.
209
+ #
210
+ # @param lines [Array<String>] an array of strings to log
211
+ # @api private
212
+ def debug_log(lines)
213
+ Array(lines).each { |line| Polytrix.logger.debug(line) }
214
+ end
208
215
  end
209
216
  end
@@ -0,0 +1,33 @@
1
+ module Polytrix
2
+ module Executors
3
+ autoload :BuffShellOutExecutor, 'polytrix/executors/buff_shellout_executor'
4
+ autoload :MixlibShellOutExecutor, 'polytrix/executors/mixlib_shellout_executor'
5
+
6
+ class ExecutionResult < Polytrix::Dash
7
+ property :exitstatus, require: true
8
+ property :stdout, required: true
9
+ property :stderr, required: true
10
+ coerce_value String, ->(v) { v.force_encoding('utf-8') }
11
+ end
12
+ end
13
+
14
+ module Executor
15
+ attr_writer :executor
16
+ attr_accessor :env
17
+
18
+ def executor
19
+ @executor ||= if RUBY_PLATFORM == 'java'
20
+ # TODO: Display warning that JRuby support is experimental
21
+ # (because executor may not be equivalent)
22
+ Polytrix::Executors::BuffShellOutExecutor.new
23
+ else
24
+ Polytrix::Executors::MixlibShellOutExecutor.new
25
+ end
26
+ end
27
+
28
+ def execute(command, opts = {})
29
+ opts[:env] = env unless env.nil?
30
+ executor.execute(command, opts)
31
+ end
32
+ end
33
+ end