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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.groc.json +7 -0
- data/.rspec +11 -0
- data/.rspec_parallel +10 -0
- data/Gemfile +23 -0
- data/README.md +48 -0
- data/Rakefile +143 -0
- data/Vagrantfile +41 -0
- data/features/0_identity_spec.rb +40 -0
- data/features/1_cloud_files_spec.rb +48 -0
- data/features/2_servers_spec.rb +19 -0
- data/features/features_helper.rb +46 -0
- data/features/helpers/cloudfiles_helper.rb +31 -0
- data/features/helpers/pacto_helper.rb +33 -0
- data/features/helpers/teardown_helper.rb +49 -0
- data/features/pacto/extensions/loaders/api_blueprint_loader.rb +63 -0
- data/features/pacto/extensions/loaders/simple_loader.rb +55 -0
- data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +17 -0
- data/features/pacto/extensions/matchers.rb +38 -0
- data/features/phase2/feature_coverage_report.rb +109 -0
- data/features/phase2/run_all_features.rb +14 -0
- data/features/static_site/fixtures/index.html +6 -0
- data/lib/polytrix/challenge.rb +27 -0
- data/lib/polytrix/challenge_builder.rb +16 -0
- data/lib/polytrix/challenge_runner.rb +87 -0
- data/lib/polytrix/configuration.rb +31 -0
- data/lib/polytrix/core/file_finder.rb +43 -0
- data/lib/polytrix/core/implementor.rb +17 -0
- data/lib/polytrix/core/result_tracker.rb +25 -0
- data/lib/polytrix/documentation_generator.rb +18 -0
- data/lib/polytrix/manifest.rb +46 -0
- data/lib/polytrix/result.rb +9 -0
- data/lib/polytrix/rspec/documentation_formatter.rb +41 -0
- data/lib/polytrix/rspec.rb +75 -0
- data/lib/polytrix/runners/linux_challenge_runner.rb +22 -0
- data/lib/polytrix/runners/middleware/change_directory.rb +20 -0
- data/lib/polytrix/runners/middleware/feature_executor.rb +23 -0
- data/lib/polytrix/runners/middleware/pacto.rb +59 -0
- data/lib/polytrix/runners/middleware/setup_env_vars.rb +38 -0
- data/lib/polytrix/runners/windows_challenge_runner.rb +25 -0
- data/lib/polytrix/version.rb +3 -0
- data/lib/polytrix.rb +56 -0
- data/packer/.gitignore +3 -0
- data/packer/Berksfile +15 -0
- data/packer/Gemfile +5 -0
- data/packer/Vagrantfile +128 -0
- data/packer/cookbooks/drg/metadata.rb +27 -0
- data/packer/cookbooks/drg/recipes/admins.rb +22 -0
- data/packer/cookbooks/drg/recipes/default.rb +9 -0
- data/packer/cookbooks/drg/recipes/dotnet.rb +4 -0
- data/packer/cookbooks/drg/recipes/golang.rb +4 -0
- data/packer/cookbooks/drg/recipes/java.rb +5 -0
- data/packer/cookbooks/drg/recipes/php.rb +10 -0
- data/packer/cookbooks/drg/recipes/ruby.rb +29 -0
- data/packer/cookbooks/drg/recipes/system.rb +13 -0
- data/packer/create_box.sh +10 -0
- data/packer/http/preseed.cfg +87 -0
- data/packer/packer.json +91 -0
- data/packer/scripts/root_setup.sh +37 -0
- data/packer/scripts/setup.sh +32 -0
- data/pacto/config/pacto_server.rb +40 -0
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +64 -0
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +100 -0
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +176 -0
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +189 -0
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +63 -0
- data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +62 -0
- data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +192 -0
- data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +39 -0
- data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +38 -0
- data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +30 -0
- data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +30 -0
- data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +114 -0
- data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +13 -0
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +46 -0
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +230 -0
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +15 -0
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +43 -0
- data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +44 -0
- data/pacto/pacto_server.rb +100 -0
- data/pacto/rackspace_uri_map.yaml +229 -0
- data/polytrix.gemspec +27 -0
- data/scripts/bootstrap +10 -0
- data/scripts/cibuild +4 -0
- data/sdks/fog/.gitignore +1 -0
- data/sdks/fog/Gemfile +5 -0
- data/sdks/fog/challenges/all_connections.rb +45 -0
- data/sdks/fog/challenges/authenticate_token.rb +15 -0
- data/sdks/fog/challenges/cdn_enable_container.rb +20 -0
- data/sdks/fog/challenges/create_a_container.rb +17 -0
- data/sdks/fog/challenges/create_server.rb +36 -0
- data/sdks/fog/challenges/get_object_metadata.rb +13 -0
- data/sdks/fog/challenges/list_containers.rb +10 -0
- data/sdks/fog/challenges/provision_scalable_webapp.rb +30 -0
- data/sdks/fog/challenges/upload_folder.rb +25 -0
- data/sdks/fog/scripts/bootstrap +4 -0
- data/sdks/fog/scripts/bootstrap.ps1 +1 -0
- data/sdks/fog/scripts/wrapper +2 -0
- data/sdks/fog/scripts/wrapper.ps1 +1 -0
- data/sdks/gophercloud/.gitignore +2 -0
- data/sdks/gophercloud/challenges/authenticate_token.go +23 -0
- data/sdks/gophercloud/scripts/bootstrap +6 -0
- data/sdks/gophercloud/scripts/wrapper +10 -0
- data/sdks/jclouds/.gitignore +1 -0
- data/sdks/jclouds/challenges/AuthenticateToken.java +115 -0
- data/sdks/jclouds/pom.xml +34 -0
- data/sdks/jclouds/scripts/bootstrap +3 -0
- data/sdks/jclouds/scripts/wrapper +7 -0
- data/sdks/openstack.net/.gitignore +4 -0
- data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
- data/sdks/openstack.net/.nuget/NuGet.Config +6 -0
- data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
- data/sdks/openstack.net/.nuget/NuGet.targets +136 -0
- data/sdks/openstack.net/Challenge.cs +10 -0
- data/sdks/openstack.net/RunChallenge.cs +19 -0
- data/sdks/openstack.net/challenges/AuthenticateToken.cs +24 -0
- data/sdks/openstack.net/challenges/Weird.cs +133 -0
- data/sdks/openstack.net/openstack.net.csproj +58 -0
- data/sdks/openstack.net/openstack.net.sln +27 -0
- data/sdks/openstack.net/openstack.net.userprefs +8 -0
- data/sdks/openstack.net/packages.config +6 -0
- data/sdks/openstack.net/scripts/bootstrap +2 -0
- data/sdks/openstack.net/scripts/bootstrap.ps1 +2 -0
- data/sdks/openstack.net/scripts/wrapper +7 -0
- data/sdks/openstack.net/scripts/wrapper.ps1 +1 -0
- data/sdks/php-opencloud/.gitignore +4 -0
- data/sdks/php-opencloud/challenges/all_connections.php +64 -0
- data/sdks/php-opencloud/challenges/authenticate_token.php +14 -0
- data/sdks/php-opencloud/challenges/create_server.php +39 -0
- data/sdks/php-opencloud/challenges/get_object_metadata.php +19 -0
- data/sdks/php-opencloud/composer.json +5 -0
- data/sdks/php-opencloud/scripts/bootstrap +4 -0
- data/sdks/php-opencloud/scripts/bootstrap.ps1 +2 -0
- data/sdks/php-opencloud/scripts/wrapper +2 -0
- data/sdks/php-opencloud/scripts/wrapper.ps1 +1 -0
- data/sdks/pkgcloud/.gitignore +1 -0
- data/sdks/pkgcloud/challenges/authenticate_token.js +17 -0
- data/sdks/pkgcloud/challenges/get_object_metadata.js +18 -0
- data/sdks/pkgcloud/scripts/bootstrap +2 -0
- data/sdks/pkgcloud/scripts/bootstrap.ps1 +1 -0
- data/sdks/pkgcloud/scripts/wrapper +2 -0
- data/sdks/pkgcloud/scripts/wrapper.ps1 +1 -0
- data/sdks/pyrax/.gitignore +2 -0
- data/sdks/pyrax/challenges/all_connections.py +61 -0
- data/sdks/pyrax/challenges/authenticate_token.py +17 -0
- data/sdks/pyrax/challenges/cdn_enable_container.py +22 -0
- data/sdks/pyrax/challenges/create_a_container.py +21 -0
- data/sdks/pyrax/challenges/create_server.py +35 -0
- data/sdks/pyrax/challenges/get_object_metadata.py +17 -0
- data/sdks/pyrax/challenges/upload_folder.py +32 -0
- data/sdks/pyrax/requirements.txt +21 -0
- data/sdks/pyrax/scripts/bootstrap +9 -0
- data/sdks/pyrax/scripts/bootstrap.ps1 +7 -0
- data/sdks/pyrax/scripts/wrapper +3 -0
- data/sdks/pyrax/scripts/wrapper.ps1 +2 -0
- data/spec/fixtures/factorial.py +18 -0
- data/spec/fixtures/polytrix.yml +16 -0
- data/spec/fixtures/src-doc/quine.md.erb +20 -0
- data/spec/polytrix/challenge_builder_spec.rb +16 -0
- data/spec/polytrix/challenge_runner_spec.rb +14 -0
- data/spec/polytrix/challenge_spec.rb +20 -0
- data/spec/polytrix/configuration_spec.rb +10 -0
- data/spec/polytrix/documentation_generator_spec.rb +36 -0
- data/spec/polytrix/file_finder_spec.rb +24 -0
- data/spec/polytrix/manifest_spec.rb +26 -0
- data/spec/polytrix/middleware/feature_executor_spec.rb +48 -0
- data/spec/polytrix_spec.rb +13 -0
- data/spec/rspec_spec.rb +17 -0
- data/spec/spec_helper.rb +7 -0
- 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,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,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
|