polytrix 0.0.1 → 0.1.0.pre

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 (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