polytrix 0.0.1 → 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (238) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -6
  4. data/.rubocop-todo.yml +19 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +11 -0
  7. data/Gemfile +0 -16
  8. data/README.md +119 -28
  9. data/Rakefile +18 -123
  10. data/bin/polytrix +5 -0
  11. data/doc-src/_markdown.md +5 -0
  12. data/doc-src/default_bootstrap.md +13 -0
  13. data/docs/influences.md +28 -0
  14. data/docs/samples/code2doc/java/HelloWorld.md +13 -0
  15. data/docs/samples/code2doc/java/Quine.md +33 -0
  16. data/docs/samples/code2doc/python/hello_world.md +3 -0
  17. data/docs/samples/code2doc/python/quine.md +4 -0
  18. data/docs/samples/code2doc/ruby/hello_world.md +7 -0
  19. data/features/execution.feature +67 -0
  20. data/features/fixtures/configs/empty.yml +12 -0
  21. data/features/fixtures/configs/hello_world.yml +10 -0
  22. data/features/fixtures/spec/polytrix_merge.rb +5 -0
  23. data/features/fixtures/spec/polytrix_spec.rb +10 -0
  24. data/features/reporting.feature +140 -0
  25. data/features/step_definitions/sdk_steps.rb +12 -0
  26. data/features/support/env.rb +8 -0
  27. data/lib/polytrix/challenge.rb +20 -7
  28. data/lib/polytrix/challenge_runner.rb +9 -44
  29. data/lib/polytrix/cli/add.rb +67 -0
  30. data/lib/polytrix/cli/report.rb +88 -0
  31. data/lib/polytrix/cli/reports/hash_reporter.rb +30 -0
  32. data/lib/polytrix/cli/reports/json_reporter.rb +14 -0
  33. data/lib/polytrix/cli/reports/markdown_reporter.rb +23 -0
  34. data/lib/polytrix/cli/reports/yaml_reporter.rb +14 -0
  35. data/lib/polytrix/cli.rb +158 -0
  36. data/lib/polytrix/configuration.rb +65 -4
  37. data/lib/polytrix/core/file_system_helper.rb +75 -0
  38. data/lib/polytrix/core/implementor.rb +31 -3
  39. data/lib/polytrix/documentation/code_segmenter.rb +168 -0
  40. data/lib/polytrix/documentation/comment_styles.rb +87 -0
  41. data/lib/polytrix/documentation/helpers/code_helper.rb +85 -0
  42. data/lib/polytrix/documentation/view_helper.rb +21 -0
  43. data/lib/polytrix/documentation_generator.rb +59 -10
  44. data/lib/polytrix/executor.rb +89 -0
  45. data/lib/polytrix/logger.rb +17 -0
  46. data/lib/polytrix/manifest.rb +64 -7
  47. data/lib/polytrix/result.rb +16 -2
  48. data/lib/polytrix/rspec/documentation_formatter.rb +41 -16
  49. data/lib/polytrix/rspec/yaml_report.rb +51 -0
  50. data/lib/polytrix/rspec.rb +32 -53
  51. data/lib/polytrix/runners/middleware/feature_executor.rb +4 -3
  52. data/lib/polytrix/runners/middleware/setup_env_vars.rb +6 -4
  53. data/lib/polytrix/validation.rb +20 -0
  54. data/lib/polytrix/validations.rb +23 -0
  55. data/lib/polytrix/validator.rb +20 -0
  56. data/lib/polytrix/validator_registry.rb +34 -0
  57. data/lib/polytrix/version.rb +1 -1
  58. data/lib/polytrix.rb +125 -22
  59. data/polytrix.gemspec +7 -2
  60. data/polytrix.rb +6 -0
  61. data/polytrix_tests.yml +20 -0
  62. data/resources/code_sample.tt +2 -0
  63. data/samples/.gitignore +2 -0
  64. data/samples/_markdown.md +5 -0
  65. data/samples/default_bootstrap.rb +14 -0
  66. data/samples/polytrix.rb +28 -0
  67. data/samples/polytrix_cli.sh +7 -0
  68. data/samples/polytrix_tests.yml +10 -0
  69. data/{sdks/fog → samples}/scripts/bootstrap +0 -2
  70. data/samples/scripts/wrapper +7 -0
  71. data/samples/sdks/custom/polytrix.yml +2 -0
  72. data/samples/sdks/java/.gitignore +2 -0
  73. data/samples/sdks/java/build.gradle +14 -0
  74. data/samples/sdks/java/challenges/HelloWorld.java +10 -0
  75. data/samples/sdks/java/challenges/Quine.java +31 -0
  76. data/samples/sdks/java/code_sample.tt +11 -0
  77. data/samples/sdks/java/scripts/bootstrap +2 -0
  78. data/samples/sdks/java/scripts/wrapper +8 -0
  79. data/samples/sdks/python/challenges/hello_world.py +2 -0
  80. data/samples/sdks/python/challenges/quine.py +2 -0
  81. data/{sdks/pkgcloud → samples/sdks/python}/scripts/wrapper +1 -1
  82. data/samples/sdks/ruby/challenges/hello_world.rb +4 -0
  83. data/scripts/bootstrap +1 -9
  84. data/scripts/wrapper +7 -0
  85. data/spec/fabricators/challenge_fabricator.rb +17 -0
  86. data/spec/fabricators/manifest_fabricator.rb +50 -0
  87. data/spec/fabricators/validator_fabricator.rb +12 -0
  88. data/spec/fixtures/{polytrix.yml → polytrix_tests.yml} +0 -0
  89. data/spec/fixtures/src-doc/_scenario.md.erb +1 -0
  90. data/spec/polytrix/challenge_runner_spec.rb +3 -3
  91. data/spec/polytrix/challenge_spec.rb +3 -4
  92. data/spec/polytrix/cli_spec.rb +39 -0
  93. data/spec/polytrix/configuration_spec.rb +45 -1
  94. data/spec/polytrix/documentation/helpers/code_helper_spec.rb +120 -0
  95. data/spec/polytrix/documentation_generator_spec.rb +41 -20
  96. data/spec/polytrix/file_finder_spec.rb +4 -3
  97. data/spec/polytrix/implementor_spec.rb +33 -0
  98. data/spec/polytrix/manifest_spec.rb +32 -14
  99. data/spec/polytrix/middleware/feature_executor_spec.rb +1 -1
  100. data/spec/polytrix/result_spec.rb +49 -0
  101. data/spec/polytrix/validations_spec.rb +16 -0
  102. data/spec/polytrix/validator_registry_spec.rb +39 -0
  103. data/spec/polytrix/validator_spec.rb +63 -0
  104. data/spec/polytrix_spec.rb +33 -7
  105. data/spec/spec_helper.rb +14 -1
  106. data/spec/thor_spy.rb +64 -0
  107. metadata +177 -160
  108. data/.rspec_parallel +0 -10
  109. data/Vagrantfile +0 -41
  110. data/features/0_identity_spec.rb +0 -40
  111. data/features/1_cloud_files_spec.rb +0 -48
  112. data/features/2_servers_spec.rb +0 -19
  113. data/features/features_helper.rb +0 -46
  114. data/features/helpers/cloudfiles_helper.rb +0 -31
  115. data/features/helpers/pacto_helper.rb +0 -33
  116. data/features/helpers/teardown_helper.rb +0 -49
  117. data/features/pacto/extensions/loaders/api_blueprint_loader.rb +0 -63
  118. data/features/pacto/extensions/loaders/simple_loader.rb +0 -55
  119. data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +0 -17
  120. data/features/pacto/extensions/matchers.rb +0 -38
  121. data/features/phase2/feature_coverage_report.rb +0 -109
  122. data/features/phase2/run_all_features.rb +0 -14
  123. data/features/static_site/fixtures/index.html +0 -6
  124. data/lib/polytrix/challenge_builder.rb +0 -16
  125. data/lib/polytrix/core/file_finder.rb +0 -43
  126. data/lib/polytrix/core/result_tracker.rb +0 -25
  127. data/lib/polytrix/runners/middleware/pacto.rb +0 -59
  128. data/packer/.gitignore +0 -3
  129. data/packer/Berksfile +0 -15
  130. data/packer/Gemfile +0 -5
  131. data/packer/Vagrantfile +0 -128
  132. data/packer/cookbooks/drg/metadata.rb +0 -27
  133. data/packer/cookbooks/drg/recipes/admins.rb +0 -22
  134. data/packer/cookbooks/drg/recipes/default.rb +0 -9
  135. data/packer/cookbooks/drg/recipes/dotnet.rb +0 -4
  136. data/packer/cookbooks/drg/recipes/golang.rb +0 -4
  137. data/packer/cookbooks/drg/recipes/java.rb +0 -5
  138. data/packer/cookbooks/drg/recipes/php.rb +0 -10
  139. data/packer/cookbooks/drg/recipes/ruby.rb +0 -29
  140. data/packer/cookbooks/drg/recipes/system.rb +0 -13
  141. data/packer/create_box.sh +0 -10
  142. data/packer/http/preseed.cfg +0 -87
  143. data/packer/packer.json +0 -91
  144. data/packer/scripts/root_setup.sh +0 -37
  145. data/packer/scripts/setup.sh +0 -32
  146. data/pacto/config/pacto_server.rb +0 -40
  147. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +0 -64
  148. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +0 -100
  149. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +0 -176
  150. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +0 -189
  151. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +0 -63
  152. data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +0 -62
  153. data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +0 -192
  154. data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +0 -39
  155. data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +0 -38
  156. data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +0 -30
  157. data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +0 -30
  158. data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +0 -114
  159. data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +0 -13
  160. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +0 -46
  161. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +0 -230
  162. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +0 -15
  163. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +0 -43
  164. data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +0 -44
  165. data/pacto/pacto_server.rb +0 -100
  166. data/pacto/rackspace_uri_map.yaml +0 -229
  167. data/scripts/cibuild +0 -4
  168. data/sdks/fog/.gitignore +0 -1
  169. data/sdks/fog/Gemfile +0 -5
  170. data/sdks/fog/challenges/all_connections.rb +0 -45
  171. data/sdks/fog/challenges/authenticate_token.rb +0 -15
  172. data/sdks/fog/challenges/cdn_enable_container.rb +0 -20
  173. data/sdks/fog/challenges/create_a_container.rb +0 -17
  174. data/sdks/fog/challenges/create_server.rb +0 -36
  175. data/sdks/fog/challenges/get_object_metadata.rb +0 -13
  176. data/sdks/fog/challenges/list_containers.rb +0 -10
  177. data/sdks/fog/challenges/provision_scalable_webapp.rb +0 -30
  178. data/sdks/fog/challenges/upload_folder.rb +0 -25
  179. data/sdks/fog/scripts/bootstrap.ps1 +0 -1
  180. data/sdks/fog/scripts/wrapper +0 -2
  181. data/sdks/fog/scripts/wrapper.ps1 +0 -1
  182. data/sdks/gophercloud/.gitignore +0 -2
  183. data/sdks/gophercloud/challenges/authenticate_token.go +0 -23
  184. data/sdks/gophercloud/scripts/bootstrap +0 -6
  185. data/sdks/gophercloud/scripts/wrapper +0 -10
  186. data/sdks/jclouds/.gitignore +0 -1
  187. data/sdks/jclouds/challenges/AuthenticateToken.java +0 -115
  188. data/sdks/jclouds/pom.xml +0 -34
  189. data/sdks/jclouds/scripts/bootstrap +0 -3
  190. data/sdks/jclouds/scripts/wrapper +0 -7
  191. data/sdks/openstack.net/.gitignore +0 -4
  192. data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
  193. data/sdks/openstack.net/.nuget/NuGet.Config +0 -6
  194. data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
  195. data/sdks/openstack.net/.nuget/NuGet.targets +0 -136
  196. data/sdks/openstack.net/Challenge.cs +0 -10
  197. data/sdks/openstack.net/RunChallenge.cs +0 -19
  198. data/sdks/openstack.net/challenges/AuthenticateToken.cs +0 -24
  199. data/sdks/openstack.net/challenges/Weird.cs +0 -133
  200. data/sdks/openstack.net/openstack.net.csproj +0 -58
  201. data/sdks/openstack.net/openstack.net.sln +0 -27
  202. data/sdks/openstack.net/openstack.net.userprefs +0 -8
  203. data/sdks/openstack.net/packages.config +0 -6
  204. data/sdks/openstack.net/scripts/bootstrap +0 -2
  205. data/sdks/openstack.net/scripts/bootstrap.ps1 +0 -2
  206. data/sdks/openstack.net/scripts/wrapper +0 -7
  207. data/sdks/openstack.net/scripts/wrapper.ps1 +0 -1
  208. data/sdks/php-opencloud/.gitignore +0 -4
  209. data/sdks/php-opencloud/challenges/all_connections.php +0 -64
  210. data/sdks/php-opencloud/challenges/authenticate_token.php +0 -14
  211. data/sdks/php-opencloud/challenges/create_server.php +0 -39
  212. data/sdks/php-opencloud/challenges/get_object_metadata.php +0 -19
  213. data/sdks/php-opencloud/composer.json +0 -5
  214. data/sdks/php-opencloud/scripts/bootstrap +0 -4
  215. data/sdks/php-opencloud/scripts/bootstrap.ps1 +0 -2
  216. data/sdks/php-opencloud/scripts/wrapper +0 -2
  217. data/sdks/php-opencloud/scripts/wrapper.ps1 +0 -1
  218. data/sdks/pkgcloud/.gitignore +0 -1
  219. data/sdks/pkgcloud/challenges/authenticate_token.js +0 -17
  220. data/sdks/pkgcloud/challenges/get_object_metadata.js +0 -18
  221. data/sdks/pkgcloud/scripts/bootstrap +0 -2
  222. data/sdks/pkgcloud/scripts/bootstrap.ps1 +0 -1
  223. data/sdks/pkgcloud/scripts/wrapper.ps1 +0 -1
  224. data/sdks/pyrax/.gitignore +0 -2
  225. data/sdks/pyrax/challenges/all_connections.py +0 -61
  226. data/sdks/pyrax/challenges/authenticate_token.py +0 -17
  227. data/sdks/pyrax/challenges/cdn_enable_container.py +0 -22
  228. data/sdks/pyrax/challenges/create_a_container.py +0 -21
  229. data/sdks/pyrax/challenges/create_server.py +0 -35
  230. data/sdks/pyrax/challenges/get_object_metadata.py +0 -17
  231. data/sdks/pyrax/challenges/upload_folder.py +0 -32
  232. data/sdks/pyrax/requirements.txt +0 -21
  233. data/sdks/pyrax/scripts/bootstrap +0 -9
  234. data/sdks/pyrax/scripts/bootstrap.ps1 +0 -7
  235. data/sdks/pyrax/scripts/wrapper +0 -3
  236. data/sdks/pyrax/scripts/wrapper.ps1 +0 -2
  237. data/spec/polytrix/challenge_builder_spec.rb +0 -16
  238. data/spec/rspec_spec.rb +0 -17
@@ -0,0 +1,85 @@
1
+ require 'polytrix/documentation/code_segmenter'
2
+
3
+ module Polytrix
4
+ module Documentation
5
+ module Helpers
6
+ module CodeHelper
7
+ class ReStructuredTextHelper
8
+ def self.code_block(source, language)
9
+ buffer = StringIO.new
10
+ buffer.puts ".. code-block:: #{language}"
11
+ indented_source = source.lines.map do|line|
12
+ " #{line}"
13
+ end.join("\n")
14
+ buffer.puts indented_source
15
+ buffer.string
16
+ end
17
+ end
18
+ class MarkdownHelper
19
+ def self.code_block(source, language)
20
+ buffer = StringIO.new
21
+ buffer.puts "```#{language}"
22
+ buffer.puts source
23
+ buffer.puts '```'
24
+ buffer.string
25
+ end
26
+ end
27
+ def initialize(*args)
28
+ @segmenter = Polytrix::Documentation::CodeSegmenter.new
29
+ super
30
+ end
31
+
32
+ def source
33
+ File.read source_file
34
+ end
35
+
36
+ def code_block(source_code, language, opts = { format: :markdown })
37
+ case opts[:format]
38
+ when :rst
39
+ ReStructuredTextHelper.code_block source_code, language
40
+ when :markdown
41
+ MarkdownHelper.code_block source_code, language
42
+ else
43
+ fail IllegalArgumentError, "Unknown format: #{format}"
44
+ end
45
+ end
46
+
47
+ # Loses proper indentation on comments
48
+ def snippet_after(matcher)
49
+ segments = @segmenter.segment(source)
50
+ buffer = StringIO.new
51
+ segment = segments.find do |s|
52
+ doc_segment_content = s.first.join
53
+ doc_segment_content.match matcher
54
+ end
55
+ buffer.print segment[1].join "\n" if segment # return code segment
56
+ buffer.string
57
+ end
58
+
59
+ def snippet_between(before_matcher, after_matcher)
60
+ segments = @segmenter.segment(source)
61
+ start_segment = find_segment_index segments, before_matcher
62
+ end_segment = find_segment_index segments, after_matcher
63
+ buffer = StringIO.new
64
+ if start_segment && end_segment
65
+ segments[start_segment...end_segment].each do |segment|
66
+ buffer.puts @segmenter.comment(segment[0]) unless segment == segments[start_segment]
67
+ buffer.puts segment[1].join
68
+ end
69
+ end
70
+ buffer.puts "\n"
71
+ buffer.string
72
+ end
73
+
74
+ private
75
+
76
+ def find_segment_index(segments, matcher)
77
+ segments.find_index do |s|
78
+ doc_segment_content = s.first.join
79
+ doc_segment_content.match matcher
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,21 @@
1
+ module Polytrix
2
+ module Documentation
3
+ module ViewHelper
4
+ def polytrix_toc
5
+ buffer = StringIO.new
6
+ buffer.puts '<ul>'
7
+ Polytrix.manifest.suites.each do |suite_name, suite|
8
+ buffer.puts "<li>#{suite_name}</li>"
9
+ buffer.puts '<ul>'
10
+ suite.samples.each do |challenge_name|
11
+ buffer.puts "<li>#{challenge_name}</li>"
12
+ end
13
+ buffer.puts '</ul>'
14
+ end
15
+ buffer.puts '</ul>'
16
+
17
+ buffer.string
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,18 +1,67 @@
1
+ require 'tilt' # seems to be a bug where padrino-helpers should require tilt
2
+ require 'padrino-helpers'
3
+
1
4
  module Polytrix
2
5
  class DocumentationGenerator
3
- include Polytrix::Core::FileFinder
4
- attr_reader :template_file
6
+ [
7
+ Padrino::Helpers::OutputHelpers,
8
+ Padrino::Helpers::AssetTagHelpers,
9
+ Padrino::Helpers::TagHelpers,
10
+ Polytrix::Documentation::Helpers::CodeHelper
11
+ ].each do | helper|
12
+ include helper
13
+ end
14
+
15
+ attr_reader :scenario
16
+
17
+ def initialize(template_file = nil, scenario = nil)
18
+ @scenario = scenario
19
+ @template_file = template_file
20
+ end
21
+
22
+ def process(challenges)
23
+ @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
29
+ end
5
30
 
6
- def initialize(search_path)
7
- @search_path = search_path
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
40
+ end
8
41
  end
9
42
 
10
- def process(scenario, challenges)
11
- @template_file = find_file @search_path, scenario, ""
12
- erb = ERB.new File.read(template_file)
13
- erb.result binding
14
- rescue Polytrix::Core::FileFinder::FileNotFound
15
- nil
43
+ def code2doc(source_file, language)
44
+ source_code = File.read(source_file)
45
+ if language.nil?
46
+ language, comment_style = Documentation::CommentStyles.infer File.extname(source_file)
47
+ segmenter_language = comment_style[:language] || language
48
+ else
49
+ segmenter_language = language
50
+ end
51
+
52
+ buffer = StringIO.new
53
+ segmenter_options = {
54
+ language: segmenter_language
55
+ }
56
+ segmenter = Polytrix::Documentation::CodeSegmenter.new(segmenter_options)
57
+ segments = segmenter.segment source_code
58
+ segments.each do |comment, code|
59
+ comment = comment.join("\n")
60
+ code = code.join("\n")
61
+ buffer.puts comment unless comment.empty?
62
+ buffer.puts code_block code, language unless code.empty?
63
+ end
64
+ buffer.string
16
65
  end
17
66
  end
18
67
  end
@@ -0,0 +1,89 @@
1
+ require 'hashie/dash'
2
+ require 'thor'
3
+
4
+ module Polytrix
5
+ module Executor
6
+ class OutputDecorator
7
+ # Reserve :red, :black, :white
8
+ COLORS = [:green, :yellow, :blue, :magenta, :cyan]
9
+
10
+ def self.next_color
11
+ @next_color ||= 0
12
+ @next_color += 1
13
+ COLORS[@next_color % COLORS.size]
14
+ end
15
+
16
+ def initialize(real_io, prefix = nil)
17
+ @real_io = real_io
18
+ # @prefix = set_color(prefix, :cyan)
19
+ @prefix = "#{prefix}: " if prefix
20
+ @color = self.class.next_color
21
+ @thor_shell = Thor::Shell::Color.new
22
+ end
23
+
24
+ def puts(line)
25
+ line = line.gsub(/^/, @prefix) if @prefix
26
+ @real_io.puts @thor_shell.set_color(line, @color)
27
+ end
28
+
29
+ def <<(line)
30
+ line = line.gsub(/^/, @prefix) if @prefix
31
+ @real_io << @thor_shell.set_color(line, @color)
32
+ end
33
+
34
+ def method_missing(meth, *args, &block)
35
+ @real_io.send meth, *args, &block
36
+ end
37
+ end
38
+
39
+ class ExecutionError < StandardError
40
+ attr_accessor :execution_result
41
+ end
42
+
43
+ class ExecutionResult < Hashie::Dash
44
+ property :exitstatus, require: true
45
+ property :stdout, required: true
46
+ property :stderr, required: true
47
+ end
48
+
49
+ class InteractiveExecutor
50
+ def execute(command, opts)
51
+ system command
52
+ # FIXME: This needs to return execution result, if interactive remains supported
53
+ end
54
+ end
55
+
56
+ class ShellOutExecutor
57
+ def execute(command, opts)
58
+ prefix = opts.delete :prefix
59
+ shell = Mixlib::ShellOut.new(command, opts)
60
+ shell.live_stream = OutputDecorator.new($stdout, prefix) unless Polytrix.configuration.suppress_output
61
+ shell.run_command
62
+ execution_result = ExecutionResult.new exitstatus: shell.exitstatus, stdout: shell.stdout, stderr: shell.stderr
63
+ begin
64
+ shell.error!
65
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
66
+ execution_error = ExecutionError.new(e)
67
+ execution_error.execution_result = execution_result
68
+ raise execution_error
69
+ end
70
+
71
+ execution_result
72
+ end
73
+ end
74
+
75
+ attr_writer :executor
76
+
77
+ def executor
78
+ @executor ||= if ENV['INTERACTIVE']
79
+ InteractiveExecutor.new
80
+ else
81
+ ShellOutExecutor.new
82
+ end
83
+ end
84
+
85
+ def execute(command, opts = {})
86
+ executor.execute(command, opts)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,17 @@
1
+ require 'logger'
2
+
3
+ module Polytrix
4
+ module ClassMethods
5
+ def logger
6
+ @logger ||= Polytrix.configuration.logger
7
+ end
8
+ end
9
+
10
+ module Logger
11
+ include ClassMethods
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+ end
17
+ end
@@ -2,13 +2,44 @@ require 'yaml'
2
2
  require 'hashie/dash'
3
3
  require 'hashie/mash'
4
4
  require 'hashie/extensions/coercion'
5
+ require 'hashie/extensions/deep_merge'
5
6
 
6
7
  module Polytrix
8
+ # Polytrix::Manifest acts as a test manifest. It defines the test scenarios that should be run,
9
+ # and may be shared across multiple implementors when used for a compliance suite.
10
+ #
11
+ # A manifest is generally defined and loaded from YAML. Here's an example manifest:
12
+ # ---
13
+ # global_env:
14
+ # LOCALE: <%= ENV['LANG'] %>
15
+ # FAVORITE_NUMBER: 5
16
+ # suites:
17
+ # Katas:
18
+ # env:
19
+ # NAME: 'Max'
20
+ # samples:
21
+ # - hello world
22
+ # - quine
23
+ # Tutorials:
24
+ # env:
25
+ # samples:
26
+ # - deploying
27
+ # - documenting
28
+ #
29
+ # The *suites* object defines the tests. Each object, under suites, like *Katas* or *Tutorials* in this
30
+ # example, represents a test suite. A test suite is subdivided into *samples*, that each act as a scenario.
31
+ # The *global_env* object and the *env* under each suite define (and standardize) the input for each test.
32
+ # The *global_env* values will be made available to all tests as environment variables, along with the *env*
33
+ # values for that specific test.
34
+ #
7
35
  class Manifest < Hashie::Dash
36
+ include Logger
37
+ include Hashie::Extensions::DeepMerge
38
+
8
39
  class Environment < Hashie::Mash
9
- # Hashie Coercion - automatically treat all values as string
40
+ # Hashie Coercion - automatically treat all values as String
10
41
  def self.coerce(obj)
11
- data = obj.inject({}) do |h, (key, value)|
42
+ data = obj.reduce({}) do |h, (key, value)|
12
43
  h[key] = value.to_s
13
44
  h
14
45
  end
@@ -16,13 +47,17 @@ module Polytrix
16
47
  end
17
48
  end
18
49
 
19
- class Suite < Hashie::Mash
50
+ class Suite < Hashie::Dash
51
+ include Hashie::Extensions::Coercion
52
+ property :env, default: {}
53
+ property :samples, default: []
54
+ property :results
20
55
  end
21
56
 
22
57
  class Suites < Hashie::Mash
23
- # Hashie Coercion - automatically treat all values as string
58
+ # Hashie Coercion - automatically treat all values as Suite
24
59
  def self.coerce(obj)
25
- data = obj.inject({}) do |h, (key, value)|
60
+ data = obj.reduce({}) do |h, (key, value)|
26
61
  h[key] = Polytrix::Manifest::Suite.new(value)
27
62
  h
28
63
  end
@@ -36,11 +71,33 @@ module Polytrix
36
71
  property :suites
37
72
  coerce_key :suites, Polytrix::Manifest::Suites
38
73
 
74
+ # Parses a YAML file to create a {Manifest} object.
39
75
  def self.from_yaml(yaml_file)
76
+ ENV['POLYTRIX_SEED'] ||= $PROCESS_ID.to_s
77
+ logger.debug "Loading #{yaml_file}"
40
78
  raw_content = File.read(yaml_file)
41
79
  processed_content = ERB.new(raw_content).result
42
- data = YAML::load processed_content
80
+ data = YAML.load processed_content
43
81
  new data
44
82
  end
83
+
84
+ def find_suite(suite_name)
85
+ _, suite = suites.find { |name, _| name.downcase == suite_name.downcase }
86
+ suite
87
+ end
88
+
89
+ def find_challenge(suite_name, scenario_name)
90
+ suite = find_suite suite_name
91
+ return nil if suite.nil?
92
+
93
+ if suite.samples.is_a? Array
94
+ # No results yet
95
+ suite.samples.find { |name, _| name.downcase == scenario_name.downcase }
96
+ Challenge.new suite: suite_name, name: scenario_name
97
+ else
98
+ _, challenge_data = find_suite('identity').samples.find { |name, challenge| name.downcase == scenario_name.downcase }
99
+ Challenge.new(suite: suite_name, name: scenario_name, result: challenge_data)
100
+ end
101
+ end
45
102
  end
46
- end
103
+ end
@@ -2,8 +2,22 @@ require 'hashie/dash'
2
2
 
3
3
  module Polytrix
4
4
  class Result < Hashie::Dash
5
- property :process, required: true
6
- property :source
5
+ extend Forwardable
6
+ include Hashie::Extensions::Coercion
7
+ property :execution_result # , required: true
8
+ def_delegators :execution_result, :stdout, :stderr, :exitstatus
9
+ property :source_file # , required: true
7
10
  property :data
11
+ property :validations, default: Validations.new
12
+ coerce_key :validations, Validations
13
+
14
+ def status
15
+ # A feature can be validated by different suites, or manually vs an automated suite.
16
+ # That's why there's a precedence rather than boolean algebra here...
17
+ return 'failed' if validations.any? { |v| v.result == 'failed' }
18
+ return 'passed' if validations.any? { |v| v.result == 'passed' }
19
+ return 'pending' if validations.any? { |v| v.result == 'pending' }
20
+ 'skipped'
21
+ end
8
22
  end
9
23
  end
@@ -6,36 +6,61 @@ require 'fileutils'
6
6
  module Polytrix
7
7
  module RSpec
8
8
  class DocumentationFormatter < ::RSpec::Core::Formatters::BaseFormatter
9
+ include Polytrix::Core::FileSystemHelper
10
+
9
11
  def initialize(output)
12
+ @templates_dir = 'doc-src'
13
+ @output_dir = 'docs'
10
14
  @results = Hashie::Mash.new
15
+ @summary_files = %w(index)
11
16
  super
12
17
  end
13
18
 
14
19
  def example_group_finished(example_group)
15
- group_names = example_group.parent_groups.map{|g| g.description}
16
- polytrix_challenges = example_group.examples.map { |e| e.metadata[:polytrix] }
17
- produce_doc example_group.description, polytrix_challenges
20
+ polytrix_challenges = example_group.examples.map { |e| e.metadata[:polytrix_challenge] }
21
+ target_file = target_file_for example_group
22
+ template_file = template_for example_group
23
+ produce_doc template_file, target_file, example_group.description, polytrix_challenges if template_file
18
24
  end
19
25
 
20
26
  def dump_summary(duration, example_count, failure_count, pending_count)
21
- doc_gen = Polytrix::DocumentationGenerator.new 'doc-src'
22
- all_challenges = examples.map{|e| e.metadata[:polytrix]}
27
+ all_challenges = examples.map { |e| e.metadata[:polytrix_challenge] }
23
28
  grouped_challenges = all_challenges.compact.group_by(&:name)
24
- produce_doc 'index', grouped_challenges
29
+ @summary_files.each do |summary_file|
30
+ template_file = template_for summary_file, use_default: false
31
+ next if template_file.nil?
32
+ target_file = target_file_for_summary(template_file)
33
+ produce_doc template_file, target_file, 'Summary', grouped_challenges
34
+ end
25
35
  end
26
36
 
27
37
  private
28
- def produce_doc(name, data)
29
- doc_gen = Polytrix::DocumentationGenerator.new 'doc-src'
30
- doc = doc_gen.process(name, data)
31
- target_file = doc_gen.template_file.to_s.gsub 'doc-src', 'docs'
32
- unless target_file.empty?
33
- FileUtils.mkdir_p File.dirname(target_file)
34
- File.open(target_file, 'wb') do |f|
35
- f.write doc
36
- end
38
+
39
+ def template_for(name, opts = { use_default: true })
40
+ name = name.description if name.respond_to? :description
41
+ begin
42
+ find_file @templates_dir, name, ''
43
+ rescue Polytrix::Core::FileSystemHelper::FileNotFound
44
+ Polytrix.configuration.default_doc_template if opts[:use_default] == true
37
45
  end
38
46
  end
47
+
48
+ def target_file_for(example_group)
49
+ names = [@output_dir].concat(example_group.parent_groups.reverse.map(&:description))
50
+ # Markdown format by default, but will be overridden to match the template
51
+ slugify(names.join File::SEPARATOR) + '.md'
52
+ end
53
+
54
+ def target_file_for_summary(template_file)
55
+ name = File.basename(template_file)
56
+ slugify("docs/#{name}")
57
+ end
58
+
59
+ def produce_doc(template_file, target_file, scenario, data)
60
+ doc_gen = Polytrix::DocumentationGenerator.new template_file, scenario
61
+ doc_gen.process data
62
+ doc_gen.save target_file
63
+ end
39
64
  end
40
65
  end
41
- end
66
+ end
@@ -0,0 +1,51 @@
1
+ require 'polytrix/rspec'
2
+ require 'hashie/mash'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Polytrix
7
+ module RSpec
8
+ class YAMLReport < ::RSpec::Core::Formatters::BaseFormatter
9
+ def example_passed(example)
10
+ add_implementation_result example, :passed
11
+ end
12
+
13
+ def example_failed(example)
14
+ add_implementation_result example, :failed
15
+ end
16
+
17
+ def example_pending(example)
18
+ add_implementation_result example, :pending
19
+ end
20
+
21
+ def dump_summary(duration, example_count, failure_count, pending_count)
22
+ results = Hashie::Mash.new(Polytrix.manifest.dup.to_hash)
23
+ all_challenges = examples.map { |e| e.metadata[:polytrix_challenge] }
24
+ grouped_challenges = all_challenges.compact.group_by(&:name)
25
+ results.suites.each do |suite_name, suite|
26
+ suite.samples = suite.samples.each_with_object({}) do |sample_name, sample_results|
27
+ sample_results[sample_name] ||= {}
28
+ if grouped_challenges[sample_name]
29
+ challenge_results = grouped_challenges[sample_name]
30
+ challenge_results.each do |challenge|
31
+ sample_results[sample_name][challenge.implementor.name] = challenge.result
32
+ end
33
+ end
34
+ end
35
+ end
36
+ @output.puts YAML.dump(results.to_hash)
37
+ end
38
+
39
+ private
40
+
41
+ def add_implementation_result(example, state)
42
+ validation = Validation.new(
43
+ validated_by: 'polytrix',
44
+ result: state.to_s
45
+ )
46
+ challenge = example.metadata[:polytrix_challenge]
47
+ challenge.result.validations << validation unless challenge.nil? || challenge.result.nil?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -9,67 +9,46 @@ end
9
9
 
10
10
  module Polytrix
11
11
  module RSpec
12
- module Helper
13
- def challenge_runner
14
- @challenge_runner ||= Polytrix::ChallengeRunner.createRunner
15
- end
16
-
17
- def execute_challenge(sdk_dir, challenge_name, vars)
18
- implementor_name = File.basename(sdk_dir) # Might not be a good assumption
19
- implementor = Polytrix.implementors.find { |i| i.name == implementor_name }
20
- challenge = ChallengeBuilder.new(implementor).build :name => challenge_name, :vars => vars, :basedir => sdk_dir, :implementor => implementor.name
21
- example.metadata[:polytrix] = challenge
22
- result = challenge.run
23
- yield result
24
- end
25
- end
26
-
27
12
  class << self
28
- def run_manifest(manifest)
29
- manifest['suites'].each do |suite_name, suite_config|
30
- describe suite_name do
31
- suite_config['samples'].each do |scenario|
32
- code_sample scenario, '', suite_config['env'].to_hash do |result|
33
- instance_exec result, &Polytrix.default_validator_callback
13
+ def shared_examples(caller) # rubocop:disable MethodLength
14
+ # FIXME: Long method because it's hard to eval in the right context
15
+ caller.instance_eval do
16
+ Polytrix.manifest['suites'].each do |suite_name, suite_config|
17
+ describe suite_name do
18
+ samples = suite_config['samples'] || []
19
+ samples.each do |scenario|
20
+ describe scenario do
21
+ Polytrix.implementors.each do |sdk|
22
+ it sdk.name, sdk.name.to_sym => true do
23
+ begin
24
+ skip "#{sdk.name} is not setup" unless File.directory? sdk.basedir
25
+ challenge = sdk.build_challenge suite: suite_name, name: scenario, vars: suite_config['env']
26
+ example.metadata[:polytrix_challenge] = challenge
27
+ challenge.run
28
+ validators = Polytrix::ValidatorRegistry.validators_for challenge
29
+ validators.each do |validator|
30
+ instance_exec challenge, &validator.callback
31
+ end
32
+ rescue Polytrix::FeatureNotImplementedError => e
33
+ skip e.message
34
+ rescue ThreadError => e
35
+ # Extra debug info for ThreadError
36
+ $stderr.puts "ThreadError detected: #{e.message}"
37
+ $stderr.puts "ThreadError backtrace: #{e.backtrace}"
38
+ fail e
39
+ end
40
+ end
41
+ end
42
+ end
34
43
  end
35
44
  end
36
45
  end
37
46
  end
38
47
  end
39
- end
40
- end
41
- end
42
-
43
- def code_sample(challenge, description = '', environment = [], services = [], &block)
44
- challenge_file = challenge.downcase.gsub(' ', '_')
45
- describe challenge, markdown: description,
46
- # :environment => redact(environment),
47
- services: services do
48
- Polytrix.implementors.each do |sdk|
49
- sdk = sdk.name if sdk.respond_to? :name
50
- it sdk, sdk.to_sym => true, 'data-challenge' => challenge_file, 'data-sdk' => sdk do
51
- Polytrix.results.example_started example
52
- begin
53
- sdk_dir = Polytrix.sdk_dir sdk
54
- pending "#{sdk} is not setup" unless File.directory? sdk_dir
55
- challenge_runner.find_challenge! challenge_file, sdk_dir
56
- execute_challenge sdk_dir, challenge_file, environment do |result|
57
- Polytrix.results.execution_result example, result
58
- instance_exec result, &block
59
- end
60
48
 
61
- rescue Polytrix::FeatureNotImplementedError => e
62
- pending e.message
63
- rescue ThreadError => e
64
- puts "ThreadError detected: #{e.message}"
65
- puts "ThreadError backtrace: #{e.backtrace}"
66
- raise e
67
- end
49
+ def run_manifest(manifest)
50
+ shared_examples(self)
68
51
  end
69
52
  end
70
53
  end
71
54
  end
72
-
73
- RSpec.configure do |c|
74
- c.include Polytrix::RSpec::Helper
75
- end
@@ -2,6 +2,7 @@ module Polytrix
2
2
  module Runners
3
3
  module Middleware
4
4
  class FeatureExecutor
5
+ include Polytrix::Core::FileSystemHelper
5
6
  def initialize(app)
6
7
  @app = app
7
8
  end
@@ -10,10 +11,10 @@ module Polytrix
10
11
  challenge_runner = env[:challenge_runner]
11
12
  env_file = env[:env_file]
12
13
  source_file = env[:source_file]
13
- relative_source_file = source_file.relative_path_from env[:basedir]
14
+ relative_source_file = relativize(source_file, env[:basedir])
14
15
  command = challenge_runner.challenge_command(env_file, relative_source_file)
15
- process = challenge_runner.run_command command
16
- env[:result] = Result.new(process: process, source: env[:source_file])
16
+ execution_result = challenge_runner.run_command command
17
+ env[:result] = Result.new(execution_result: execution_result, source_file: env[:source_file].to_s)
17
18
  @app.call env
18
19
  env[:result]
19
20
  end