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.
- checksums.yaml +4 -4
- data/.gitignore +2 -2
- data/.rspec +1 -6
- data/.rubocop-todo.yml +19 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +11 -0
- data/Gemfile +0 -16
- data/README.md +119 -28
- data/Rakefile +18 -123
- data/bin/polytrix +5 -0
- data/doc-src/_markdown.md +5 -0
- data/doc-src/default_bootstrap.md +13 -0
- data/docs/influences.md +28 -0
- data/docs/samples/code2doc/java/HelloWorld.md +13 -0
- data/docs/samples/code2doc/java/Quine.md +33 -0
- data/docs/samples/code2doc/python/hello_world.md +3 -0
- data/docs/samples/code2doc/python/quine.md +4 -0
- data/docs/samples/code2doc/ruby/hello_world.md +7 -0
- data/features/execution.feature +67 -0
- data/features/fixtures/configs/empty.yml +12 -0
- data/features/fixtures/configs/hello_world.yml +10 -0
- data/features/fixtures/spec/polytrix_merge.rb +5 -0
- data/features/fixtures/spec/polytrix_spec.rb +10 -0
- data/features/reporting.feature +140 -0
- data/features/step_definitions/sdk_steps.rb +12 -0
- data/features/support/env.rb +8 -0
- data/lib/polytrix/challenge.rb +20 -7
- data/lib/polytrix/challenge_runner.rb +9 -44
- data/lib/polytrix/cli/add.rb +67 -0
- data/lib/polytrix/cli/report.rb +88 -0
- data/lib/polytrix/cli/reports/hash_reporter.rb +30 -0
- data/lib/polytrix/cli/reports/json_reporter.rb +14 -0
- data/lib/polytrix/cli/reports/markdown_reporter.rb +23 -0
- data/lib/polytrix/cli/reports/yaml_reporter.rb +14 -0
- data/lib/polytrix/cli.rb +158 -0
- data/lib/polytrix/configuration.rb +65 -4
- data/lib/polytrix/core/file_system_helper.rb +75 -0
- data/lib/polytrix/core/implementor.rb +31 -3
- data/lib/polytrix/documentation/code_segmenter.rb +168 -0
- data/lib/polytrix/documentation/comment_styles.rb +87 -0
- data/lib/polytrix/documentation/helpers/code_helper.rb +85 -0
- data/lib/polytrix/documentation/view_helper.rb +21 -0
- data/lib/polytrix/documentation_generator.rb +59 -10
- data/lib/polytrix/executor.rb +89 -0
- data/lib/polytrix/logger.rb +17 -0
- data/lib/polytrix/manifest.rb +64 -7
- data/lib/polytrix/result.rb +16 -2
- data/lib/polytrix/rspec/documentation_formatter.rb +41 -16
- data/lib/polytrix/rspec/yaml_report.rb +51 -0
- data/lib/polytrix/rspec.rb +32 -53
- data/lib/polytrix/runners/middleware/feature_executor.rb +4 -3
- data/lib/polytrix/runners/middleware/setup_env_vars.rb +6 -4
- data/lib/polytrix/validation.rb +20 -0
- data/lib/polytrix/validations.rb +23 -0
- data/lib/polytrix/validator.rb +20 -0
- data/lib/polytrix/validator_registry.rb +34 -0
- data/lib/polytrix/version.rb +1 -1
- data/lib/polytrix.rb +125 -22
- data/polytrix.gemspec +7 -2
- data/polytrix.rb +6 -0
- data/polytrix_tests.yml +20 -0
- data/resources/code_sample.tt +2 -0
- data/samples/.gitignore +2 -0
- data/samples/_markdown.md +5 -0
- data/samples/default_bootstrap.rb +14 -0
- data/samples/polytrix.rb +28 -0
- data/samples/polytrix_cli.sh +7 -0
- data/samples/polytrix_tests.yml +10 -0
- data/{sdks/fog → samples}/scripts/bootstrap +0 -2
- data/samples/scripts/wrapper +7 -0
- data/samples/sdks/custom/polytrix.yml +2 -0
- data/samples/sdks/java/.gitignore +2 -0
- data/samples/sdks/java/build.gradle +14 -0
- data/samples/sdks/java/challenges/HelloWorld.java +10 -0
- data/samples/sdks/java/challenges/Quine.java +31 -0
- data/samples/sdks/java/code_sample.tt +11 -0
- data/samples/sdks/java/scripts/bootstrap +2 -0
- data/samples/sdks/java/scripts/wrapper +8 -0
- data/samples/sdks/python/challenges/hello_world.py +2 -0
- data/samples/sdks/python/challenges/quine.py +2 -0
- data/{sdks/pkgcloud → samples/sdks/python}/scripts/wrapper +1 -1
- data/samples/sdks/ruby/challenges/hello_world.rb +4 -0
- data/scripts/bootstrap +1 -9
- data/scripts/wrapper +7 -0
- data/spec/fabricators/challenge_fabricator.rb +17 -0
- data/spec/fabricators/manifest_fabricator.rb +50 -0
- data/spec/fabricators/validator_fabricator.rb +12 -0
- data/spec/fixtures/{polytrix.yml → polytrix_tests.yml} +0 -0
- data/spec/fixtures/src-doc/_scenario.md.erb +1 -0
- data/spec/polytrix/challenge_runner_spec.rb +3 -3
- data/spec/polytrix/challenge_spec.rb +3 -4
- data/spec/polytrix/cli_spec.rb +39 -0
- data/spec/polytrix/configuration_spec.rb +45 -1
- data/spec/polytrix/documentation/helpers/code_helper_spec.rb +120 -0
- data/spec/polytrix/documentation_generator_spec.rb +41 -20
- data/spec/polytrix/file_finder_spec.rb +4 -3
- data/spec/polytrix/implementor_spec.rb +33 -0
- data/spec/polytrix/manifest_spec.rb +32 -14
- data/spec/polytrix/middleware/feature_executor_spec.rb +1 -1
- data/spec/polytrix/result_spec.rb +49 -0
- data/spec/polytrix/validations_spec.rb +16 -0
- data/spec/polytrix/validator_registry_spec.rb +39 -0
- data/spec/polytrix/validator_spec.rb +63 -0
- data/spec/polytrix_spec.rb +33 -7
- data/spec/spec_helper.rb +14 -1
- data/spec/thor_spy.rb +64 -0
- metadata +177 -160
- data/.rspec_parallel +0 -10
- data/Vagrantfile +0 -41
- data/features/0_identity_spec.rb +0 -40
- data/features/1_cloud_files_spec.rb +0 -48
- data/features/2_servers_spec.rb +0 -19
- data/features/features_helper.rb +0 -46
- data/features/helpers/cloudfiles_helper.rb +0 -31
- data/features/helpers/pacto_helper.rb +0 -33
- data/features/helpers/teardown_helper.rb +0 -49
- data/features/pacto/extensions/loaders/api_blueprint_loader.rb +0 -63
- data/features/pacto/extensions/loaders/simple_loader.rb +0 -55
- data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +0 -17
- data/features/pacto/extensions/matchers.rb +0 -38
- data/features/phase2/feature_coverage_report.rb +0 -109
- data/features/phase2/run_all_features.rb +0 -14
- data/features/static_site/fixtures/index.html +0 -6
- data/lib/polytrix/challenge_builder.rb +0 -16
- data/lib/polytrix/core/file_finder.rb +0 -43
- data/lib/polytrix/core/result_tracker.rb +0 -25
- data/lib/polytrix/runners/middleware/pacto.rb +0 -59
- data/packer/.gitignore +0 -3
- data/packer/Berksfile +0 -15
- data/packer/Gemfile +0 -5
- data/packer/Vagrantfile +0 -128
- data/packer/cookbooks/drg/metadata.rb +0 -27
- data/packer/cookbooks/drg/recipes/admins.rb +0 -22
- data/packer/cookbooks/drg/recipes/default.rb +0 -9
- data/packer/cookbooks/drg/recipes/dotnet.rb +0 -4
- data/packer/cookbooks/drg/recipes/golang.rb +0 -4
- data/packer/cookbooks/drg/recipes/java.rb +0 -5
- data/packer/cookbooks/drg/recipes/php.rb +0 -10
- data/packer/cookbooks/drg/recipes/ruby.rb +0 -29
- data/packer/cookbooks/drg/recipes/system.rb +0 -13
- data/packer/create_box.sh +0 -10
- data/packer/http/preseed.cfg +0 -87
- data/packer/packer.json +0 -91
- data/packer/scripts/root_setup.sh +0 -37
- data/packer/scripts/setup.sh +0 -32
- data/pacto/config/pacto_server.rb +0 -40
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +0 -64
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +0 -100
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +0 -176
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +0 -189
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +0 -63
- data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +0 -62
- data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +0 -192
- data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +0 -39
- data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +0 -38
- data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +0 -30
- data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +0 -30
- data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +0 -114
- data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +0 -13
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +0 -46
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +0 -230
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +0 -15
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +0 -43
- data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +0 -44
- data/pacto/pacto_server.rb +0 -100
- data/pacto/rackspace_uri_map.yaml +0 -229
- data/scripts/cibuild +0 -4
- data/sdks/fog/.gitignore +0 -1
- data/sdks/fog/Gemfile +0 -5
- data/sdks/fog/challenges/all_connections.rb +0 -45
- data/sdks/fog/challenges/authenticate_token.rb +0 -15
- data/sdks/fog/challenges/cdn_enable_container.rb +0 -20
- data/sdks/fog/challenges/create_a_container.rb +0 -17
- data/sdks/fog/challenges/create_server.rb +0 -36
- data/sdks/fog/challenges/get_object_metadata.rb +0 -13
- data/sdks/fog/challenges/list_containers.rb +0 -10
- data/sdks/fog/challenges/provision_scalable_webapp.rb +0 -30
- data/sdks/fog/challenges/upload_folder.rb +0 -25
- data/sdks/fog/scripts/bootstrap.ps1 +0 -1
- data/sdks/fog/scripts/wrapper +0 -2
- data/sdks/fog/scripts/wrapper.ps1 +0 -1
- data/sdks/gophercloud/.gitignore +0 -2
- data/sdks/gophercloud/challenges/authenticate_token.go +0 -23
- data/sdks/gophercloud/scripts/bootstrap +0 -6
- data/sdks/gophercloud/scripts/wrapper +0 -10
- data/sdks/jclouds/.gitignore +0 -1
- data/sdks/jclouds/challenges/AuthenticateToken.java +0 -115
- data/sdks/jclouds/pom.xml +0 -34
- data/sdks/jclouds/scripts/bootstrap +0 -3
- data/sdks/jclouds/scripts/wrapper +0 -7
- data/sdks/openstack.net/.gitignore +0 -4
- data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
- data/sdks/openstack.net/.nuget/NuGet.Config +0 -6
- data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
- data/sdks/openstack.net/.nuget/NuGet.targets +0 -136
- data/sdks/openstack.net/Challenge.cs +0 -10
- data/sdks/openstack.net/RunChallenge.cs +0 -19
- data/sdks/openstack.net/challenges/AuthenticateToken.cs +0 -24
- data/sdks/openstack.net/challenges/Weird.cs +0 -133
- data/sdks/openstack.net/openstack.net.csproj +0 -58
- data/sdks/openstack.net/openstack.net.sln +0 -27
- data/sdks/openstack.net/openstack.net.userprefs +0 -8
- data/sdks/openstack.net/packages.config +0 -6
- data/sdks/openstack.net/scripts/bootstrap +0 -2
- data/sdks/openstack.net/scripts/bootstrap.ps1 +0 -2
- data/sdks/openstack.net/scripts/wrapper +0 -7
- data/sdks/openstack.net/scripts/wrapper.ps1 +0 -1
- data/sdks/php-opencloud/.gitignore +0 -4
- data/sdks/php-opencloud/challenges/all_connections.php +0 -64
- data/sdks/php-opencloud/challenges/authenticate_token.php +0 -14
- data/sdks/php-opencloud/challenges/create_server.php +0 -39
- data/sdks/php-opencloud/challenges/get_object_metadata.php +0 -19
- data/sdks/php-opencloud/composer.json +0 -5
- data/sdks/php-opencloud/scripts/bootstrap +0 -4
- data/sdks/php-opencloud/scripts/bootstrap.ps1 +0 -2
- data/sdks/php-opencloud/scripts/wrapper +0 -2
- data/sdks/php-opencloud/scripts/wrapper.ps1 +0 -1
- data/sdks/pkgcloud/.gitignore +0 -1
- data/sdks/pkgcloud/challenges/authenticate_token.js +0 -17
- data/sdks/pkgcloud/challenges/get_object_metadata.js +0 -18
- data/sdks/pkgcloud/scripts/bootstrap +0 -2
- data/sdks/pkgcloud/scripts/bootstrap.ps1 +0 -1
- data/sdks/pkgcloud/scripts/wrapper.ps1 +0 -1
- data/sdks/pyrax/.gitignore +0 -2
- data/sdks/pyrax/challenges/all_connections.py +0 -61
- data/sdks/pyrax/challenges/authenticate_token.py +0 -17
- data/sdks/pyrax/challenges/cdn_enable_container.py +0 -22
- data/sdks/pyrax/challenges/create_a_container.py +0 -21
- data/sdks/pyrax/challenges/create_server.py +0 -35
- data/sdks/pyrax/challenges/get_object_metadata.py +0 -17
- data/sdks/pyrax/challenges/upload_folder.py +0 -32
- data/sdks/pyrax/requirements.txt +0 -21
- data/sdks/pyrax/scripts/bootstrap +0 -9
- data/sdks/pyrax/scripts/bootstrap.ps1 +0 -7
- data/sdks/pyrax/scripts/wrapper +0 -3
- data/sdks/pyrax/scripts/wrapper.ps1 +0 -2
- data/spec/polytrix/challenge_builder_spec.rb +0 -16
- data/spec/rspec_spec.rb +0 -17
data/lib/polytrix/cli.rb
ADDED
@@ -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 :
|
26
|
-
property :
|
27
|
-
property :
|
28
|
-
|
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
|
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
|