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,158 @@
1
+ require 'polytrix'
2
+ require 'thor'
3
+
4
+ module Polytrix
5
+ module CLI
6
+ autoload :Add, 'polytrix/cli/add'
7
+ autoload :Report, 'polytrix/cli/report'
8
+
9
+ class Base < Thor
10
+ include Polytrix::Core::FileSystemHelper
11
+
12
+ def self.config_options
13
+ # I had trouble with class_option and subclasses...
14
+ method_option :manifest, type: 'string', default: 'polytrix_tests.yml', desc: 'The Polytrix test manifest file'
15
+ method_option :config, type: 'string', default: 'polytrix.rb', desc: 'The Polytrix config file'
16
+ end
17
+
18
+ def self.log_options
19
+ method_option :quiet, type: :boolean, default: false, desc: 'Do not print log messages'
20
+ end
21
+
22
+ def self.doc_options
23
+ method_option :target_dir, type: :string, default: 'docs'
24
+ method_option :lang, enum: Polytrix::Documentation::CommentStyles::COMMENT_STYLES.keys, desc: 'Source language (auto-detected if not specified)'
25
+ method_option :format, enum: %w(md rst), default: 'md'
26
+ end
27
+
28
+ def self.sdk_options
29
+ method_option :sdk, type: 'string', desc: 'An implementor name or directory', default: '.'
30
+ end
31
+
32
+ protected
33
+
34
+ def find_sdks(sdks)
35
+ sdks.map do |sdk|
36
+ implementor = Polytrix.implementors.find { |i| i.name == sdk }
37
+ abort "SDK #{sdk} not found" if implementor.nil?
38
+ implementor
39
+ end
40
+ end
41
+
42
+ def pick_implementor(sdk)
43
+ Polytrix.implementors.find { |i| i.name == sdk } || Polytrix.configuration.implementor(sdk)
44
+ end
45
+
46
+ def debug(msg)
47
+ say("polytrix::debug: #{msg}", :cyan) if debugging?
48
+ end
49
+
50
+ def debugging?
51
+ !ENV['POLYTRIX_DEBUG'].nil?
52
+ end
53
+
54
+ def setup
55
+ manifest_file = File.expand_path options[:manifest]
56
+ config_file = File.expand_path options[:config]
57
+ if File.exists? manifest_file
58
+ debug "Loading manifest file: #{manifest_file}"
59
+ Polytrix.configuration.test_manifest = manifest_file if File.exists? manifest_file
60
+ end
61
+ if File.exists? config_file
62
+ debug "Loading Polytrix config: #{config_file}"
63
+ require_relative config_file
64
+ end
65
+ end
66
+ end
67
+
68
+ class Main < Base
69
+ include Polytrix::Documentation::Helpers::CodeHelper
70
+
71
+ # register Add, :add, 'add', 'Add implementors or code samples'
72
+ # register Report, :report, 'report', 'Generate test reports'
73
+ desc 'add', 'Add implementors or code samples'
74
+ subcommand 'add', Add
75
+
76
+ desc 'report', 'Generate test reports'
77
+ subcommand 'report', Report
78
+
79
+ desc 'code2doc FILES', 'Converts annotated code to Markdown or reStructuredText'
80
+ doc_options
81
+ def code2doc(*files)
82
+ if files.empty?
83
+ help('code2doc')
84
+ abort 'No FILES were specified, check usage above'
85
+ end
86
+
87
+ files.each do |file|
88
+ target_file_name = File.basename(file, File.extname(file)) + ".#{options[:format]}"
89
+ target_file = File.join(options[:target_dir], target_file_name)
90
+ say_status 'polytrix:code2doc', "Converting #{file} to #{target_file}", !quiet?
91
+ doc = Polytrix::DocumentationGenerator.new.code2doc(file, options[:lang])
92
+ FileUtils.mkdir_p File.dirname(target_file)
93
+ File.write(target_file, doc)
94
+ end
95
+ rescue Polytrix::Documentation::CommentStyles::UnknownStyleError => e
96
+ abort "Unknown file extension: #{e.extension}, please use --lang to set the language manually"
97
+ end
98
+
99
+ desc 'exec', 'Executes code sample(s), using the SDK settings if provided'
100
+ method_option :code2doc, type: :boolean, desc: 'Convert successfully executed code samples to documentation using the code2doc command'
101
+ doc_options
102
+ sdk_options
103
+ config_options
104
+ def exec(*files)
105
+ setup
106
+ if files.empty?
107
+ help('exec')
108
+ abort 'No FILES were specified, check usage above'
109
+ end
110
+
111
+ exec_options = {
112
+ # default_implementor: pick_implementor(options[:sdk])
113
+ }
114
+
115
+ files.each do | file |
116
+ say_status 'polytrix:exec', "Running #{file}..."
117
+ results = Polytrix.exec(file, exec_options)
118
+ display_results results
119
+ code2doc(file) if options[:code2doc]
120
+ end
121
+ end
122
+
123
+ desc 'bootstrap [SDKs]', 'Bootstraps the SDK by installing dependencies'
124
+ config_options
125
+ def bootstrap(*sdks)
126
+ setup
127
+ Polytrix.bootstrap(*sdks)
128
+ rescue ArgumentError => e
129
+ abort e.message
130
+ end
131
+
132
+ desc 'test [SDKs]', 'Runs and tests the code samples'
133
+ method_option :rspec_options, format: 'string', desc: 'Extra options to pass to rspec'
134
+ config_options
135
+ def test(*sdks)
136
+ setup
137
+ implementors = find_sdks(sdks)
138
+ Polytrix.configuration.rspec_options = options[:rspec_options]
139
+ Polytrix.run_tests(implementors)
140
+ end
141
+
142
+ protected
143
+
144
+ def quiet?
145
+ options[:quiet] || false
146
+ end
147
+
148
+ def display_results(challenge)
149
+ short_name = challenge.name
150
+ exit_code = challenge.result.execution_result.exitstatus
151
+ color = exit_code == 0 ? :green : :red
152
+ stderr = challenge.result.execution_result.stderr
153
+ say_status "polytrix:exec[#{short_name}][stderr]", stderr, !quiet? unless stderr.empty?
154
+ say_status "polytrix:exec[#{short_name}]", "Finished with exec code: #{challenge.result.execution_result.exitstatus}", color unless quiet?
155
+ end
156
+ end
157
+ end
158
+ end
@@ -4,6 +4,7 @@ require 'hashie/dash'
4
4
  require 'hashie/extensions/coercion'
5
5
 
6
6
  module Polytrix
7
+ RESOURCES_DIR = File.expand_path '../../../resources', __FILE__
7
8
  # Autoload pool
8
9
  module Runners
9
10
  module Middleware
@@ -22,10 +23,70 @@ module Polytrix
22
23
  class Configuration < Hashie::Dash
23
24
  include Hashie::Extensions::Coercion
24
25
 
25
- property :logger, :default => Logger.new($stdout)
26
- property :middleware, :default => Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
27
- property :implementors
28
- coerce_key :implementors, Polytrix::Implementor
26
+ property :dry_run, default: false
27
+ property :log_level, default: 'info'
28
+ property :middleware, default: Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
29
+ property :implementors, default: []
30
+ # coerce_key :implementors, Polytrix::Implementor
31
+ property :suppress_output, default: false
32
+ property :default_doc_template
33
+ property :template_dir, default: "#{RESOURCES_DIR}"
34
+ # Extra options for rspec
35
+ property :rspec_options, default: ''
29
36
 
37
+ def logger
38
+ @logger ||= ::Logger.new($stdout).tap do |logger|
39
+ levels = {
40
+ 'fatal' => ::Logger::FATAL,
41
+ 'error' => ::Logger::ERROR,
42
+ 'warn' => ::Logger::WARN,
43
+ 'info' => ::Logger::INFO,
44
+ 'debug' => ::Logger::DEBUG
45
+ }
46
+ fail "Unknown log level: #{log_level}" unless levels.keys.include? log_level
47
+ logger.level = levels[log_level]
48
+ end
49
+ end
50
+
51
+ def test_manifest
52
+ @test_manifest ||= Manifest.from_yaml 'polytrix_tests.yml'
53
+ end
54
+
55
+ def test_manifest=(yaml_file)
56
+ @test_manifest = Manifest.from_yaml yaml_file
57
+ end
58
+
59
+ def implementor(metadata)
60
+ if metadata.is_a? Hash # load from data
61
+ Implementor.new(metadata).tap do |implementor|
62
+ implementors << implementor
63
+ end
64
+ else # load from filesystem
65
+ folder = metadata
66
+ fail ArgumentError, "#{folder} is not a directory" unless File.directory? folder
67
+ settings_file = File.expand_path('polytrix.yml', folder)
68
+ if File.exist? settings_file
69
+ settings = YAML.load(File.read(settings_file))
70
+ Polytrix.configuration.implementor(settings.merge(basedir: folder))
71
+ else
72
+ Polytrix.configuration.implementor name: File.basename(folder), basedir: folder
73
+ end
74
+ end
75
+ end
76
+
77
+ # The callback used to validate code samples that
78
+ # don't have a custom validator. The default
79
+ # checks that the sample code runs successfully.
80
+ def default_validator_callback
81
+ @default_validator_callback ||= proc do |challenge|
82
+ expect(challenge[:result].execution_result.exitstatus).to eq(0)
83
+ end
84
+ end
85
+
86
+ def default_validator
87
+ @default_validator ||= Validator.new(suite: //, scenario: //, &default_validator_callback)
88
+ end
89
+
90
+ attr_writer :default_validator_callback
30
91
  end
31
92
  end
@@ -0,0 +1,75 @@
1
+ module Polytrix
2
+ module Core
3
+ module FileSystemHelper
4
+ include Polytrix::Logger
5
+ class FileNotFound < StandardError; end
6
+
7
+ # Finds a file by loosely matching the file name to a scenario name
8
+ def find_file(search_path, scenario_name, ignored_patterns = read_gitignore(search_path))
9
+ glob_string = "#{search_path}/**/*#{slugify(scenario_name)}.*"
10
+ potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
11
+ potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
12
+ potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
13
+
14
+ # Find the first file, not including generated files
15
+ file = potential_files.find do |f|
16
+ !ignored? ignored_patterns, search_path, f
17
+ end
18
+
19
+ fail FileNotFound, "No file was found for #{scenario_name} within #{search_path}" if file.nil?
20
+ Pathname.new file
21
+ end
22
+
23
+ def slugify(path)
24
+ path.downcase.gsub(' ', '_')
25
+ end
26
+
27
+ def recursive_parent_search(path, file_name = nil, &block)
28
+ if block_given?
29
+ obj = yield path
30
+ return obj if obj
31
+ elsif file_name
32
+ file = File.expand_path(file_name, path)
33
+ logger.debug "Checking for #{file}"
34
+ found = File.exists? file
35
+ else
36
+ fail ArgumentError, 'Provide either a file_name to search for, or a block to check directories'
37
+ end
38
+
39
+ parent_dir = File.dirname(path)
40
+ return path if found
41
+ return nil if parent_dir == path # we've reached the top
42
+ recursive_parent_search(parent_dir, file_name, &block)
43
+ end
44
+
45
+ private
46
+
47
+ def read_gitignore(dir)
48
+ gitignore_file = "#{dir}/.gitignore"
49
+ File.read(gitignore_file)
50
+ rescue
51
+ ''
52
+ end
53
+
54
+ def ignored?(ignored_patterns, base_path, target_file)
55
+ # Trying to match the git ignore rules but there's some discrepencies.
56
+ ignored_patterns.split.find do |pattern|
57
+ # if git ignores a folder, we should ignore all files it contains
58
+ pattern = "#{pattern}**" if pattern[-1] == '/'
59
+ started_with_slash = pattern.start_with? '/'
60
+
61
+ pattern.gsub!(/\A\//, '') # remove leading slashes since we're searching from root
62
+ file = relativize(target_file, base_path)
63
+ ignored = file.fnmatch? pattern
64
+ ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
65
+ end
66
+ end
67
+
68
+ def relativize(file, base_path)
69
+ absolute_file = File.absolute_path(file)
70
+ absolute_base_path = File.absolute_path(base_path)
71
+ Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -2,16 +2,44 @@ require 'hashie/dash'
2
2
  require 'hashie/extensions/coercion'
3
3
 
4
4
  module Polytrix
5
+ class FeatureNotImplementedError < StandardError
6
+ def initialize(feature)
7
+ super "Feature #{feature} is not implemented"
8
+ end
9
+ end
5
10
  class Implementor < Hashie::Dash
11
+ include Polytrix::Logger
12
+ include Polytrix::Core::FileSystemHelper
6
13
  include Hashie::Extensions::Coercion
14
+ include Polytrix::Executor
7
15
  property :name
8
- property :basedir
16
+ property :basedir, required: true
9
17
  property :language
10
18
  coerce_key :basedir, Pathname
11
19
 
12
20
  def initialize(data)
13
- data[:basedir] ||= "sdks/#{data[:name]}"
21
+ data = Hashie::Mash.new data
22
+ data[:name] ||= File.basename data[:basedir]
23
+ data[:basedir] = File.absolute_path(data[:basedir])
14
24
  super(data)
15
25
  end
26
+
27
+ def bootstrap
28
+ execute('./scripts/bootstrap', cwd: basedir, prefix: name)
29
+ rescue Errno::ENOENT
30
+ logger.warn "Skipping bootstrapping for #{name}, no script/bootstrap exists"
31
+ end
32
+
33
+ def build_challenge(challenge_data)
34
+ challenge_data[:source_file] ||= find_file basedir, challenge_data[:name]
35
+ challenge_data[:basedir] ||= basedir
36
+ challenge_data[:source_file] = relativize(challenge_data[:source_file], challenge_data[:basedir])
37
+ challenge_data[:implementor] ||= self
38
+ challenge_data[:suite] ||= ''
39
+ fail FeatureNotImplementedError, "#{name} is not setup" unless File.directory? challenge_data[:basedir]
40
+ Challenge.new challenge_data
41
+ rescue Polytrix::Core::FileSystemHelper::FileNotFound
42
+ raise FeatureNotImplementedError, challenge_data[:name]
43
+ end
16
44
  end
17
- end
45
+ end
@@ -0,0 +1,168 @@
1
+ require 'polytrix/documentation/comment_styles'
2
+
3
+ module Polytrix
4
+ module Documentation
5
+ # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
6
+ # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
7
+ class CodeSegmenter # rubocop:disable all
8
+ # Cops are disabled because the code is from Rocco
9
+ include CommentStyles
10
+
11
+ DEFAULT_OPTIONS = {
12
+ language: 'rb',
13
+ comment_chars: '#'
14
+ }
15
+
16
+ def initialize(options = {})
17
+ @options = DEFAULT_OPTIONS.merge options
18
+ @options[:comment_chars] = generate_comment_chars if @options[:comment_chars].is_a? String
19
+ end
20
+ # Internal Parsing and Highlighting
21
+ # ---------------------------------
22
+
23
+ # Parse the raw file source_code into a list of two-tuples. Each tuple has the
24
+ # form `[docs, code]` where both elements are arrays containing the
25
+ # raw lines parsed from the input file, comment characters stripped.
26
+ def segment(source_code)
27
+ sections, docs, code = [], [], []
28
+ lines = source_code.split("\n")
29
+
30
+ # The first line is ignored if it is a shebang line. We also ignore the
31
+ # PEP 263 encoding information in python sourcefiles, and the similar ruby
32
+ # 1.9 syntax.
33
+ lines.shift if lines[0] =~ /^\#\!/
34
+ lines.shift if lines[0] =~ /coding[:=]\s*[-\w.]+/ &&
35
+ %w(python rb).include?(@options[:language])
36
+
37
+ # To detect both block comments and single-line comments, we'll set
38
+ # up a tiny state machine, and loop through each line of the file.
39
+ # This requires an `in_comment_block` boolean, and a few regular
40
+ # expressions for line tests. We'll do the same for fake heredoc parsing.
41
+ in_comment_block = false
42
+ in_heredoc = false
43
+ single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
44
+ nil, nil, nil, nil
45
+ if not @options[:comment_chars][:single].nil?
46
+ single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
47
+ end
48
+ if not @options[:comment_chars][:multi].nil?
49
+ block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
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*$")
52
+ block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
53
+ block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
54
+ if @options[:comment_chars][:multi][:middle]
55
+ block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
56
+ end
57
+ end
58
+ if not @options[:comment_chars][:heredoc].nil?
59
+ heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
60
+ end
61
+ lines.each do |line|
62
+ # If we're currently in a comment block, check whether the line matches
63
+ # the _end_ of a comment block or the _end_ of a comment block with a
64
+ # comment.
65
+ if in_comment_block
66
+ if block_comment_end && line.match(block_comment_end)
67
+ in_comment_block = false
68
+ elsif block_comment_end_with && line.match(block_comment_end_with)
69
+ in_comment_block = false
70
+ docs << line.match(block_comment_end_with).captures.first.
71
+ sub(block_comment_mid || '', '')
72
+ else
73
+ docs << line.sub(block_comment_mid || '', '')
74
+ end
75
+ # If we're currently in a heredoc, we're looking for the end of the
76
+ # heredoc, and everything it contains is code.
77
+ elsif in_heredoc
78
+ if line.match(Regexp.new("^#{Regexp.escape(in_heredoc)}$"))
79
+ in_heredoc = false
80
+ end
81
+ code << line
82
+ # Otherwise, check whether the line starts a heredoc. If so, note the end
83
+ # pattern, and the line is code. Otherwise check whether the line matches
84
+ # the beginning of a block, or a single-line comment all on it's lonesome.
85
+ # In either case, if there's code, start a new section.
86
+ else
87
+ if heredoc_start && line.match(heredoc_start)
88
+ in_heredoc = Regexp.last_match[1]
89
+ code << line
90
+ elsif block_comment_one_liner && line.match(block_comment_one_liner)
91
+ if code.any?
92
+ sections << [docs, code]
93
+ docs, code = [], []
94
+ end
95
+ docs << line.match(block_comment_one_liner).captures.first
96
+ elsif block_comment_start && line.match(block_comment_start)
97
+ in_comment_block = true
98
+ if code.any?
99
+ sections << [docs, code]
100
+ docs, code = [], []
101
+ end
102
+ elsif block_comment_start_with && line.match(block_comment_start_with)
103
+ in_comment_block = true
104
+ if code.any?
105
+ sections << [docs, code]
106
+ docs, code = [], []
107
+ end
108
+ docs << line.match(block_comment_start_with).captures.first
109
+ elsif single_line_comment && line.match(single_line_comment)
110
+ if code.any?
111
+ sections << [docs, code]
112
+ docs, code = [], []
113
+ end
114
+ docs << line.sub(single_line_comment || '', '')
115
+ else
116
+ code << line
117
+ end
118
+ end
119
+ end
120
+ sections << [docs, code] if docs.any? || code.any?
121
+ normalize_leading_spaces(sections)
122
+ end
123
+
124
+ # Normalizes documentation whitespace by checking for leading whitespace,
125
+ # removing it, and then removing the same amount of whitespace from each
126
+ # succeeding line. That is:
127
+ #
128
+ # def func():
129
+ # """
130
+ # Comment 1
131
+ # Comment 2
132
+ # """
133
+ # print "omg!"
134
+ #
135
+ # should yield a comment block of `Comment 1\nComment 2` and code of
136
+ # `def func():\n print "omg!"`
137
+ def normalize_leading_spaces(sections)
138
+ sections.map do |section|
139
+ if section.any? && section[0].any?
140
+ leading_space = section[0][0].match("^\s+")
141
+ if leading_space
142
+ section[0] =
143
+ section[0].map { |line| line.sub(/^#{leading_space.to_s}/, '') }
144
+ end
145
+ end
146
+ section
147
+ end
148
+ end
149
+
150
+ def comment(lines)
151
+ lines.map do | line |
152
+ "#{@options[:comment_chars][:single]} #{line}"
153
+ end.join "\n"
154
+ end
155
+
156
+ private
157
+
158
+ def generate_comment_chars
159
+ @_commentchar ||=
160
+ if COMMENT_STYLES[@options[:language]]
161
+ COMMENT_STYLES[@options[:language]]
162
+ else
163
+ { single: @options[:comment_chars], multi: nil, heredoc: nil }
164
+ end
165
+ end
166
+ end # rubocop:enable all
167
+ end
168
+ end
@@ -0,0 +1,87 @@
1
+ module Polytrix
2
+ module Documentation
3
+ # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
4
+ # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
5
+ module CommentStyles
6
+ class UnknownStyleError < StandardError
7
+ attr_accessor :extension
8
+
9
+ def initialize(extension)
10
+ @extension = extension
11
+ end
12
+ end
13
+
14
+ def self.infer(extension)
15
+ extension.tr! '.', ''
16
+ return extension, COMMENT_STYLES[extension] if COMMENT_STYLES.key? extension
17
+
18
+ COMMENT_STYLES.each do | style_name, style |
19
+ return extension, style if style[:extensions].include? extension
20
+ end
21
+
22
+ fail UnknownStyleError, extension
23
+ end
24
+
25
+ C_STYLE_COMMENTS = {
26
+ single: '//',
27
+ multi: { start: '/**', middle: '*', end: '*/' },
28
+ heredoc: nil,
29
+ extensions: %w(c cpp cs java js php scala go)
30
+ }
31
+
32
+ COMMENT_STYLES = {
33
+ 'bash' => { single: '#', multi: nil, extensions: %w(sh) },
34
+ 'c' => C_STYLE_COMMENTS,
35
+ 'coffee-script' => {
36
+ single: '#',
37
+ multi: { start: '###', middle: nil, end: '###' },
38
+ heredoc: nil,
39
+ extensions: %w(coffee)
40
+ },
41
+ 'cpp' => C_STYLE_COMMENTS,
42
+ 'csharp' => C_STYLE_COMMENTS,
43
+ 'css' => {
44
+ single: nil,
45
+ multi: { start: '/**', middle: '*', end: '*/' },
46
+ heredoc: nil,
47
+ extensions: %w(css scss sass)
48
+ },
49
+ 'html' => {
50
+ single: nil,
51
+ multi: { start: '<!--', middle: nil, end: '-->' },
52
+ heredoc: nil,
53
+ extensions: %w(html htm)
54
+ },
55
+ 'java' => C_STYLE_COMMENTS,
56
+ 'js' => C_STYLE_COMMENTS,
57
+ 'lua' => {
58
+ single: '--',
59
+ multi: nil,
60
+ heredoc: nil,
61
+ extensions: %w(lua)
62
+ },
63
+ 'php' => C_STYLE_COMMENTS,
64
+ 'python' => {
65
+ single: '#',
66
+ multi: { start: '"""', middle: nil, end: '"""' },
67
+ heredoc: nil,
68
+ extensions: %w(py)
69
+ },
70
+ 'rb' => {
71
+ single: '#',
72
+ multi: { start: '=begin', middle: nil, end: '=end', idiomatic: false },
73
+ heredoc: '<<-',
74
+ extensions: %w(rb)
75
+ },
76
+ 'scala' => C_STYLE_COMMENTS,
77
+ 'scheme' => { single: ';;', multi: nil, heredoc: nil, extensions: %w(schema) },
78
+ 'xml' => {
79
+ single: nil,
80
+ multi: { start: '<!--', middle: nil, end: '-->' },
81
+ heredoc: nil,
82
+ extensions: %w(xml xsl xslt)
83
+ }
84
+ }
85
+ end
86
+ end
87
+ end