polytrix 0.0.1

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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.groc.json +7 -0
  4. data/.rspec +11 -0
  5. data/.rspec_parallel +10 -0
  6. data/Gemfile +23 -0
  7. data/README.md +48 -0
  8. data/Rakefile +143 -0
  9. data/Vagrantfile +41 -0
  10. data/features/0_identity_spec.rb +40 -0
  11. data/features/1_cloud_files_spec.rb +48 -0
  12. data/features/2_servers_spec.rb +19 -0
  13. data/features/features_helper.rb +46 -0
  14. data/features/helpers/cloudfiles_helper.rb +31 -0
  15. data/features/helpers/pacto_helper.rb +33 -0
  16. data/features/helpers/teardown_helper.rb +49 -0
  17. data/features/pacto/extensions/loaders/api_blueprint_loader.rb +63 -0
  18. data/features/pacto/extensions/loaders/simple_loader.rb +55 -0
  19. data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +17 -0
  20. data/features/pacto/extensions/matchers.rb +38 -0
  21. data/features/phase2/feature_coverage_report.rb +109 -0
  22. data/features/phase2/run_all_features.rb +14 -0
  23. data/features/static_site/fixtures/index.html +6 -0
  24. data/lib/polytrix/challenge.rb +27 -0
  25. data/lib/polytrix/challenge_builder.rb +16 -0
  26. data/lib/polytrix/challenge_runner.rb +87 -0
  27. data/lib/polytrix/configuration.rb +31 -0
  28. data/lib/polytrix/core/file_finder.rb +43 -0
  29. data/lib/polytrix/core/implementor.rb +17 -0
  30. data/lib/polytrix/core/result_tracker.rb +25 -0
  31. data/lib/polytrix/documentation_generator.rb +18 -0
  32. data/lib/polytrix/manifest.rb +46 -0
  33. data/lib/polytrix/result.rb +9 -0
  34. data/lib/polytrix/rspec/documentation_formatter.rb +41 -0
  35. data/lib/polytrix/rspec.rb +75 -0
  36. data/lib/polytrix/runners/linux_challenge_runner.rb +22 -0
  37. data/lib/polytrix/runners/middleware/change_directory.rb +20 -0
  38. data/lib/polytrix/runners/middleware/feature_executor.rb +23 -0
  39. data/lib/polytrix/runners/middleware/pacto.rb +59 -0
  40. data/lib/polytrix/runners/middleware/setup_env_vars.rb +38 -0
  41. data/lib/polytrix/runners/windows_challenge_runner.rb +25 -0
  42. data/lib/polytrix/version.rb +3 -0
  43. data/lib/polytrix.rb +56 -0
  44. data/packer/.gitignore +3 -0
  45. data/packer/Berksfile +15 -0
  46. data/packer/Gemfile +5 -0
  47. data/packer/Vagrantfile +128 -0
  48. data/packer/cookbooks/drg/metadata.rb +27 -0
  49. data/packer/cookbooks/drg/recipes/admins.rb +22 -0
  50. data/packer/cookbooks/drg/recipes/default.rb +9 -0
  51. data/packer/cookbooks/drg/recipes/dotnet.rb +4 -0
  52. data/packer/cookbooks/drg/recipes/golang.rb +4 -0
  53. data/packer/cookbooks/drg/recipes/java.rb +5 -0
  54. data/packer/cookbooks/drg/recipes/php.rb +10 -0
  55. data/packer/cookbooks/drg/recipes/ruby.rb +29 -0
  56. data/packer/cookbooks/drg/recipes/system.rb +13 -0
  57. data/packer/create_box.sh +10 -0
  58. data/packer/http/preseed.cfg +87 -0
  59. data/packer/packer.json +91 -0
  60. data/packer/scripts/root_setup.sh +37 -0
  61. data/packer/scripts/setup.sh +32 -0
  62. data/pacto/config/pacto_server.rb +40 -0
  63. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +64 -0
  64. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +100 -0
  65. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +176 -0
  66. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +189 -0
  67. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +63 -0
  68. data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +62 -0
  69. data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +192 -0
  70. data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +39 -0
  71. data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +38 -0
  72. data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +30 -0
  73. data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +30 -0
  74. data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +114 -0
  75. data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +13 -0
  76. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +46 -0
  77. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +230 -0
  78. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +15 -0
  79. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +43 -0
  80. data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +44 -0
  81. data/pacto/pacto_server.rb +100 -0
  82. data/pacto/rackspace_uri_map.yaml +229 -0
  83. data/polytrix.gemspec +27 -0
  84. data/scripts/bootstrap +10 -0
  85. data/scripts/cibuild +4 -0
  86. data/sdks/fog/.gitignore +1 -0
  87. data/sdks/fog/Gemfile +5 -0
  88. data/sdks/fog/challenges/all_connections.rb +45 -0
  89. data/sdks/fog/challenges/authenticate_token.rb +15 -0
  90. data/sdks/fog/challenges/cdn_enable_container.rb +20 -0
  91. data/sdks/fog/challenges/create_a_container.rb +17 -0
  92. data/sdks/fog/challenges/create_server.rb +36 -0
  93. data/sdks/fog/challenges/get_object_metadata.rb +13 -0
  94. data/sdks/fog/challenges/list_containers.rb +10 -0
  95. data/sdks/fog/challenges/provision_scalable_webapp.rb +30 -0
  96. data/sdks/fog/challenges/upload_folder.rb +25 -0
  97. data/sdks/fog/scripts/bootstrap +4 -0
  98. data/sdks/fog/scripts/bootstrap.ps1 +1 -0
  99. data/sdks/fog/scripts/wrapper +2 -0
  100. data/sdks/fog/scripts/wrapper.ps1 +1 -0
  101. data/sdks/gophercloud/.gitignore +2 -0
  102. data/sdks/gophercloud/challenges/authenticate_token.go +23 -0
  103. data/sdks/gophercloud/scripts/bootstrap +6 -0
  104. data/sdks/gophercloud/scripts/wrapper +10 -0
  105. data/sdks/jclouds/.gitignore +1 -0
  106. data/sdks/jclouds/challenges/AuthenticateToken.java +115 -0
  107. data/sdks/jclouds/pom.xml +34 -0
  108. data/sdks/jclouds/scripts/bootstrap +3 -0
  109. data/sdks/jclouds/scripts/wrapper +7 -0
  110. data/sdks/openstack.net/.gitignore +4 -0
  111. data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
  112. data/sdks/openstack.net/.nuget/NuGet.Config +6 -0
  113. data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
  114. data/sdks/openstack.net/.nuget/NuGet.targets +136 -0
  115. data/sdks/openstack.net/Challenge.cs +10 -0
  116. data/sdks/openstack.net/RunChallenge.cs +19 -0
  117. data/sdks/openstack.net/challenges/AuthenticateToken.cs +24 -0
  118. data/sdks/openstack.net/challenges/Weird.cs +133 -0
  119. data/sdks/openstack.net/openstack.net.csproj +58 -0
  120. data/sdks/openstack.net/openstack.net.sln +27 -0
  121. data/sdks/openstack.net/openstack.net.userprefs +8 -0
  122. data/sdks/openstack.net/packages.config +6 -0
  123. data/sdks/openstack.net/scripts/bootstrap +2 -0
  124. data/sdks/openstack.net/scripts/bootstrap.ps1 +2 -0
  125. data/sdks/openstack.net/scripts/wrapper +7 -0
  126. data/sdks/openstack.net/scripts/wrapper.ps1 +1 -0
  127. data/sdks/php-opencloud/.gitignore +4 -0
  128. data/sdks/php-opencloud/challenges/all_connections.php +64 -0
  129. data/sdks/php-opencloud/challenges/authenticate_token.php +14 -0
  130. data/sdks/php-opencloud/challenges/create_server.php +39 -0
  131. data/sdks/php-opencloud/challenges/get_object_metadata.php +19 -0
  132. data/sdks/php-opencloud/composer.json +5 -0
  133. data/sdks/php-opencloud/scripts/bootstrap +4 -0
  134. data/sdks/php-opencloud/scripts/bootstrap.ps1 +2 -0
  135. data/sdks/php-opencloud/scripts/wrapper +2 -0
  136. data/sdks/php-opencloud/scripts/wrapper.ps1 +1 -0
  137. data/sdks/pkgcloud/.gitignore +1 -0
  138. data/sdks/pkgcloud/challenges/authenticate_token.js +17 -0
  139. data/sdks/pkgcloud/challenges/get_object_metadata.js +18 -0
  140. data/sdks/pkgcloud/scripts/bootstrap +2 -0
  141. data/sdks/pkgcloud/scripts/bootstrap.ps1 +1 -0
  142. data/sdks/pkgcloud/scripts/wrapper +2 -0
  143. data/sdks/pkgcloud/scripts/wrapper.ps1 +1 -0
  144. data/sdks/pyrax/.gitignore +2 -0
  145. data/sdks/pyrax/challenges/all_connections.py +61 -0
  146. data/sdks/pyrax/challenges/authenticate_token.py +17 -0
  147. data/sdks/pyrax/challenges/cdn_enable_container.py +22 -0
  148. data/sdks/pyrax/challenges/create_a_container.py +21 -0
  149. data/sdks/pyrax/challenges/create_server.py +35 -0
  150. data/sdks/pyrax/challenges/get_object_metadata.py +17 -0
  151. data/sdks/pyrax/challenges/upload_folder.py +32 -0
  152. data/sdks/pyrax/requirements.txt +21 -0
  153. data/sdks/pyrax/scripts/bootstrap +9 -0
  154. data/sdks/pyrax/scripts/bootstrap.ps1 +7 -0
  155. data/sdks/pyrax/scripts/wrapper +3 -0
  156. data/sdks/pyrax/scripts/wrapper.ps1 +2 -0
  157. data/spec/fixtures/factorial.py +18 -0
  158. data/spec/fixtures/polytrix.yml +16 -0
  159. data/spec/fixtures/src-doc/quine.md.erb +20 -0
  160. data/spec/polytrix/challenge_builder_spec.rb +16 -0
  161. data/spec/polytrix/challenge_runner_spec.rb +14 -0
  162. data/spec/polytrix/challenge_spec.rb +20 -0
  163. data/spec/polytrix/configuration_spec.rb +10 -0
  164. data/spec/polytrix/documentation_generator_spec.rb +36 -0
  165. data/spec/polytrix/file_finder_spec.rb +24 -0
  166. data/spec/polytrix/manifest_spec.rb +26 -0
  167. data/spec/polytrix/middleware/feature_executor_spec.rb +48 -0
  168. data/spec/polytrix_spec.rb +13 -0
  169. data/spec/rspec_spec.rb +17 -0
  170. data/spec/spec_helper.rb +7 -0
  171. metadata +325 -0
@@ -0,0 +1,55 @@
1
+ require 'multi_json'
2
+ require 'yaml'
3
+ require 'pacto'
4
+ require_relative 'yaml_or_json_loader'
5
+
6
+ class Pacto::Extensions::Loaders::URIMapLoader < Pacto::Extensions::Loaders::YamlOrJsonLoader
7
+ class << self
8
+ include Pacto::Logger
9
+
10
+ def load(file)
11
+ data = super
12
+ contracts = []
13
+ data['services'].each do | group_name, service_group |
14
+ service_group['servers'].each do | server |
15
+ if service_group['services']
16
+ service_group['services'].each_pair do | service_name, service_definition|
17
+ contract = build_simple_contract service_definition, group_name, service_name, server, file
18
+ Pacto.contract_registry.register(contract)
19
+ contracts << contract
20
+ end
21
+ end
22
+ end
23
+ end
24
+ Pacto::ContractList.new contracts
25
+ end
26
+
27
+ private
28
+ def build_simple_contract service_definition, group_name, service_name, server, file
29
+ service_signature = "#{service_definition['method'].upcase} #{service_definition['uriTemplate']}"
30
+ logger.debug "Building contract for '#{service_signature}' as '#{group_name} - #{service_name}' on #{server}"
31
+ # FIXME: What about scheme?
32
+ host = "https://#{server}"
33
+ request_clause = Pacto::RequestClause.new(host, {
34
+ 'method' => service_definition['method'],
35
+ 'headers' => [], #not supporting this yet, probably needs conversion
36
+ 'path' => convert_template(service_definition['uriTemplate']),
37
+ 'body' => service_definition['requestSchema'] || {}
38
+ })
39
+ response_clause = Pacto::ResponseClause.new({
40
+ 'status' => service_definition['responseStatusCode'] || 200,
41
+ 'headers' => [], #not supporting this yet, probably needs conversion
42
+ 'body' => service_definition['responseSchema'] || {}
43
+ })
44
+ Pacto::Contract.new(request_clause, response_clause, file, service_name)
45
+ end
46
+
47
+ def convert_template path
48
+ Addressable::Template.new(path) if path
49
+ # path.gsub(/{(\w+)}/, ':\1') if path
50
+ end
51
+ end
52
+ end
53
+
54
+ # Pacto.configuration.strict_matchers = false
55
+ # contracts = Pacto::Extensions::Loaders::URIMapLoader.load('pacto/rackspace_uri_map.yaml')
@@ -0,0 +1,17 @@
1
+ module Pacto
2
+ module Extensions
3
+ module Loaders
4
+ class YamlOrJsonLoader
5
+ YAML_EXTENSIONS = %w{.yml .yaml}
6
+ def self.load(file)
7
+ raw_data = File.read file
8
+ if YAML_EXTENSIONS.include? File.extname(file)
9
+ YAML::load(raw_data)
10
+ else
11
+ MultiJson.load(raw_data)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ RSpec::Matchers.define :have_validated_service do |group_name, service_name|
2
+ @requested_name = service_name
3
+ @contract = Pacto.contract_registry.find{ |c| c.name == @requested_name }
4
+ match do
5
+ unless @contract.nil?
6
+ @validations = Pacto::ValidationRegistry.instance.validations.select {|v|
7
+ # FIXME: Same contract on multiple servers is currently problematic
8
+ v.contract && v.contract.name == @contract.name
9
+ }
10
+ !(@validations.empty? || @validations.map(&:successful?).include?(false))
11
+ end
12
+ end
13
+
14
+ failure_message_for_should do
15
+ buffer = StringIO.new
16
+ buffer.puts "expected Pacto to have validated #{@requested_name}"
17
+ if @contract.nil?
18
+ buffer.puts ' but no known contract matches that name'
19
+ elsif @validations.empty?
20
+ buffer.puts ' but no request matched the pattern'
21
+ buffer.puts " pattern: #{@contract.request_pattern}"
22
+ buffer.puts ' received:'
23
+ buffer.puts "#{WebMock::RequestRegistry.instance}"
24
+ elsif @validations.map(&:successful?).include?(false)
25
+ buffer.puts ' but validation errors were found:'
26
+ buffer.print ' '
27
+ validation_results = @validations.map(&:results).flatten.compact
28
+ buffer.puts validation_results.join "\n "
29
+ validation_results.each do |validation_result|
30
+ buffer.puts " #{validation_result}"
31
+ end
32
+ else
33
+ # FIXME: ensure this is unreachable?
34
+ buffer.puts ' but an unknown problem occurred'
35
+ end
36
+ buffer.string
37
+ end
38
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+ require 'csv'
4
+
5
+ class CSVFeatureMatrix
6
+ def initialize(matrix_csv)
7
+ @feature_matrix = []
8
+
9
+ CSV.foreach(matrix_csv, :headers => :first_row) do |row|
10
+
11
+ # Carry the product
12
+ @product = row['Product'] if row['Product']
13
+ row['Product'] ||= @product
14
+
15
+ next if row['Feature'].nil? or row['Feature'].empty?
16
+
17
+ # Normalize status
18
+ SDKs.each do |sdk|
19
+ row[sdk] = '' unless row[sdk] == 'Done'
20
+ end
21
+
22
+ @feature_matrix << row.to_hash
23
+ end
24
+ end
25
+
26
+ def products
27
+ @feature_matrix.map{|f| f['Product']}.uniq.compact
28
+ end
29
+
30
+ def features(product)
31
+ @feature_matrix.select{|f| f['Product'] == product}.map{|f| f['Feature']}.compact
32
+ end
33
+
34
+ def implementers product, service_name
35
+ code_sample = @feature_matrix.find{|f| f['Feature'] == service_name}
36
+ feature.keys.select{|k| feature[k] == 'Done'}
37
+ end
38
+ end
39
+
40
+ class CoveredFeatures
41
+ def initialize(coverage_files)
42
+ @coverage = {}
43
+ @covered_features = {}
44
+ [*coverage_files].each do |file|
45
+ @coverage.merge! YAML::load(File.read(file))
46
+ end
47
+ @coverage.values.flatten.uniq do |covered_feature|
48
+ @covered_features[covered_feature] = SDKs.select{|sdk|
49
+ @coverage.select{|k,v| k =~ /#{sdk}$/}.values.flatten.include? covered_feature
50
+ }
51
+ end
52
+ end
53
+
54
+ def coverers(product, feature)
55
+ @covered_features[feature] || []
56
+ end
57
+ end
58
+
59
+ sdk_coverage = CoveredFeatures.new(Dir['reports/api_coverage*.yaml'])
60
+ original_feature_matrix = CSVFeatureMatrix.new 'original_feature_matrix.csv'
61
+
62
+ original_feature_matrix.products.each do |product|
63
+ describe product do
64
+ original_feature_matrix.features(product).each do |feature|
65
+ describe feature do
66
+ coverers = sdk_coverage.coverers(product, feature)
67
+ implementers = original_feature_matrix.implementers(product,feature)
68
+ SDKs.each do |sdk|
69
+ it sdk, sdk.to_sym do
70
+ if coverers.include? sdk
71
+ # pass
72
+ else
73
+ if implementers.include? sdk
74
+ pending
75
+ else
76
+ fail
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # data = YAML::load(File.read('pacto/rackspace_uri_map.yaml'))
87
+ # data['services'].each do |service_group_name, service_group|
88
+ # describe service_group_name do
89
+ # services = service_group['services'] || []
90
+ # services.each do |service_name, service|
91
+ # describe service_name do
92
+ # SDKs.each do |sdk|
93
+ # it sdk, sdk.to_sym do
94
+ # sdk_coverage = coverage.select{|k,v| k =~ /#{sdk}$/ }
95
+ # if sdk_coverage.values.flatten.include? service_name
96
+ # # pass
97
+ # else
98
+ # if original_feature_matrix.implemented? service_name, sdk
99
+ # pending
100
+ # else
101
+ # fail
102
+ # end
103
+ # end
104
+ # end
105
+ # end
106
+ # end
107
+ # end
108
+ # end
109
+ # end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+
4
+ data = YAML::load(File.read('pacto/rackspace_uri_map.yaml'))
5
+ data['services'].each do |service_group_name, service_group|
6
+ describe service_group_name do
7
+ services = service_group['services'] || []
8
+ services.each do |service_name, service|
9
+ code_sample service_name, '', standard_env_vars, [] do
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,6 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ <h1>Hello, World!</h1>
5
+ </body>
6
+ </html>
@@ -0,0 +1,27 @@
1
+ require 'hashie/dash'
2
+ require 'hashie/extensions/coercion'
3
+ require 'hashie/extensions/indifferent_access'
4
+
5
+ module Polytrix
6
+ class Challenge < Hashie::Dash
7
+ include Hashie::Extensions::Coercion
8
+ include Hashie::Extensions::IndifferentAccess
9
+ property :name
10
+ property :implementor
11
+ property :vars, :default => {}
12
+ property :source_file
13
+ coerce_key :source_file, Pathname
14
+ property :basedir
15
+ coerce_key :basedir, Pathname
16
+ property :challenge_runner, :default => ChallengeRunner.createRunner
17
+ property :result
18
+ property :env_file
19
+ coerce_key :vars, Polytrix::Manifest::Environment
20
+ property :plugin_data, :default => {}
21
+
22
+ def run
23
+ challenge_runner.run_challenge self
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Polytrix
2
+ class ChallengeBuilder
3
+ include Polytrix::Core::FileFinder
4
+
5
+ def initialize(implementor)
6
+ @implementor = implementor
7
+ end
8
+
9
+ def build(challenge_data)
10
+ challenge_data[:source_file] ||= find_file @implementor.basedir, challenge_data[:name]
11
+ challenge_data[:basedir] ||= @implementor.basedir
12
+ challenge_data[:implementor] ||= @implementor.name
13
+ Challenge.new challenge_data
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,87 @@
1
+ require 'polytrix'
2
+ require 'mixlib/shellout'
3
+ require 'rbconfig'
4
+
5
+ module Polytrix
6
+ module Runners
7
+ autoload :LinuxChallengeRunner, 'polytrix/runners/linux_challenge_runner'
8
+ autoload :WindowsChallengeRunner, 'polytrix/runners/windows_challenge_runner'
9
+ end
10
+
11
+ class FeatureNotImplementedError < StandardError
12
+ def initialize(feature)
13
+ super "Feature #{feature} is not implemented"
14
+ end
15
+ end
16
+
17
+ class ChallengeRunner
18
+ include Polytrix::Core::FileFinder
19
+
20
+ def self.createRunner
21
+ case RbConfig::CONFIG['host_os']
22
+ when /mswin(\d+)|mingw/i
23
+ Runners::WindowsChallengeRunner.new
24
+ else
25
+ Runners::LinuxChallengeRunner.new
26
+ end
27
+ end
28
+
29
+ def editor_enabled?
30
+ !challenge_editor.nil?
31
+ end
32
+
33
+ def challenge_editor
34
+ ENV['CHALLENGE_EDITOR']
35
+ end
36
+
37
+ def interactive?
38
+ ENV['INTERACTIVE']
39
+ end
40
+
41
+ def show_output?
42
+ ENV['SHOW_OUTPUT']
43
+ end
44
+
45
+ def run_command(command)
46
+ if interactive? # allows use of pry, code.interact, etc.
47
+ system command
48
+ else # better error messages and interrupt handling
49
+ challenge_process = Mixlib::ShellOut.new(command)
50
+ challenge_process.live_stream = $stdout if show_output?
51
+ challenge_process.run_command
52
+ challenge_process.error!
53
+ challenge_process
54
+ end
55
+ end
56
+
57
+ def run_challenge(challenge)
58
+ middleware.call(challenge)
59
+ challenge
60
+ end
61
+
62
+ def find_challenge!(challenge, basedir = Dir.pwd)
63
+ find_file basedir, challenge
64
+ rescue Polytrix::Core::FileFinder::FileNotFound
65
+ raise FeatureNotImplementedError, challenge
66
+ end
67
+
68
+ def edit_challenge(challenge)
69
+ suffix = infer_suffix File.dirname(challenge)
70
+ challenge_file = "#{challenge}#{suffix}"
71
+ puts "Would you like to create #{challenge_file} (y/n)? "
72
+ system "#{challenge_editor} #{challenge_file}" if $stdin.gets.strip == 'y'
73
+ File.absolute_path challenge_file
74
+ end
75
+
76
+ def infer_suffix(source_dir)
77
+ # FIXME: Should be configurable or have a better way to infer
78
+ Dir["#{source_dir}/**/*.*"].map { |f| File.extname f }.first
79
+ end
80
+
81
+ private
82
+
83
+ def middleware
84
+ Polytrix.configuration.middleware
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,31 @@
1
+ require 'middleware'
2
+ require 'logger'
3
+ require 'hashie/dash'
4
+ require 'hashie/extensions/coercion'
5
+
6
+ module Polytrix
7
+ # Autoload pool
8
+ module Runners
9
+ module Middleware
10
+ autoload :FeatureExecutor, 'polytrix/runners/middleware/feature_executor'
11
+ autoload :SetupEnvVars, 'polytrix/runners/middleware/setup_env_vars'
12
+ autoload :ChangeDirectory, 'polytrix/runners/middleware/change_directory'
13
+
14
+ STANDARD_MIDDLEWARE = ::Middleware::Builder.new do
15
+ use Polytrix::Runners::Middleware::ChangeDirectory
16
+ use Polytrix::Runners::Middleware::SetupEnvVars
17
+ use Polytrix::Runners::Middleware::FeatureExecutor
18
+ end
19
+ end
20
+ end
21
+
22
+ class Configuration < Hashie::Dash
23
+ include Hashie::Extensions::Coercion
24
+
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
29
+
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ module Polytrix
2
+ module Core
3
+ module FileFinder
4
+ class FileNotFound < StandardError; end
5
+
6
+ # Finds a file by loosely matching the file name to a scenario name
7
+ def find_file(search_path, scenario_name, ignored_patterns = read_gitignore(search_path))
8
+ potential_files = Dir.glob("#{search_path}/**/*#{scenario_name}.*", File::FNM_CASEFOLD)
9
+ potential_files.concat Dir.glob("#{search_path}/**/*#{scenario_name.gsub(' ', '_')}.*", File::FNM_CASEFOLD)
10
+ potential_files.concat Dir.glob("#{search_path}/**/*#{scenario_name.gsub('_', '')}.*", File::FNM_CASEFOLD)
11
+
12
+ # Find the first file, not including generated files
13
+ file = potential_files.find { |file|
14
+ !ignored? ignored_patterns, search_path, file
15
+ }
16
+
17
+ fail FileNotFound, "No file was found for #{scenario_name} within #{search_path}" if file.nil?
18
+ Pathname.new file
19
+ end
20
+
21
+ private
22
+
23
+ def read_gitignore(dir)
24
+ gitignore_file = "#{dir}/.gitignore"
25
+ File.read(gitignore_file)
26
+ rescue
27
+ ""
28
+ end
29
+
30
+ def ignored?(ignored_patterns, base_path, target_file)
31
+ ignored_patterns.split.find do |pattern|
32
+ # if git ignores a folder, we should ignore all files it contains
33
+ pattern = "#{pattern}**" if pattern[-1] == '/'
34
+ relativize(target_file, base_path).fnmatch? pattern
35
+ end
36
+ end
37
+
38
+ def relativize(file, base_path)
39
+ Pathname.new(file).relative_path_from Pathname.new(base_path)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ require 'hashie/dash'
2
+ require 'hashie/extensions/coercion'
3
+
4
+ module Polytrix
5
+ class Implementor < Hashie::Dash
6
+ include Hashie::Extensions::Coercion
7
+ property :name
8
+ property :basedir
9
+ property :language
10
+ coerce_key :basedir, Pathname
11
+
12
+ def initialize(data)
13
+ data[:basedir] ||= "sdks/#{data[:name]}"
14
+ super(data)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'singleton'
2
+ require 'hashie/mash'
3
+
4
+ module Polytrix
5
+ class ResultTracker
6
+ include Singleton
7
+
8
+ attr_reader :results
9
+
10
+ def example_started(example)
11
+ data_for(example)[example.description] = Hashie::Mash.new
12
+ end
13
+
14
+ def execution_result(example, result)
15
+ data_for(example)[example.description][:execution_result] = result
16
+ end
17
+
18
+ private
19
+ def data_for(example)
20
+ @results ||= Hashie::Mash.new
21
+ group_names = example.example_group.parent_groups.map{|g| g.description}
22
+ group_names.inject(@results, :initializing_reader)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module Polytrix
2
+ class DocumentationGenerator
3
+ include Polytrix::Core::FileFinder
4
+ attr_reader :template_file
5
+
6
+ def initialize(search_path)
7
+ @search_path = search_path
8
+ end
9
+
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
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ require 'yaml'
2
+ require 'hashie/dash'
3
+ require 'hashie/mash'
4
+ require 'hashie/extensions/coercion'
5
+
6
+ module Polytrix
7
+ class Manifest < Hashie::Dash
8
+ class Environment < Hashie::Mash
9
+ # Hashie Coercion - automatically treat all values as string
10
+ def self.coerce(obj)
11
+ data = obj.inject({}) do |h, (key, value)|
12
+ h[key] = value.to_s
13
+ h
14
+ end
15
+ new data
16
+ end
17
+ end
18
+
19
+ class Suite < Hashie::Mash
20
+ end
21
+
22
+ class Suites < Hashie::Mash
23
+ # Hashie Coercion - automatically treat all values as string
24
+ def self.coerce(obj)
25
+ data = obj.inject({}) do |h, (key, value)|
26
+ h[key] = Polytrix::Manifest::Suite.new(value)
27
+ h
28
+ end
29
+ new data
30
+ end
31
+ end
32
+
33
+ include Hashie::Extensions::Coercion
34
+ property :global_env
35
+ coerce_key :global_env, Polytrix::Manifest::Environment
36
+ property :suites
37
+ coerce_key :suites, Polytrix::Manifest::Suites
38
+
39
+ def self.from_yaml(yaml_file)
40
+ raw_content = File.read(yaml_file)
41
+ processed_content = ERB.new(raw_content).result
42
+ data = YAML::load processed_content
43
+ new data
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ require 'hashie/dash'
2
+
3
+ module Polytrix
4
+ class Result < Hashie::Dash
5
+ property :process, required: true
6
+ property :source
7
+ property :data
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ require 'polytrix/rspec'
2
+ require 'hashie/mash'
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module Polytrix
7
+ module RSpec
8
+ class DocumentationFormatter < ::RSpec::Core::Formatters::BaseFormatter
9
+ def initialize(output)
10
+ @results = Hashie::Mash.new
11
+ super
12
+ end
13
+
14
+ 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
18
+ end
19
+
20
+ 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]}
23
+ grouped_challenges = all_challenges.compact.group_by(&:name)
24
+ produce_doc 'index', grouped_challenges
25
+ end
26
+
27
+ 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
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end