polytrix 0.0.1 → 0.1.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'polytrix/documentation/code_segmenter'
|
|
2
|
+
|
|
3
|
+
module Polytrix
|
|
4
|
+
module Documentation
|
|
5
|
+
module Helpers
|
|
6
|
+
module CodeHelper
|
|
7
|
+
class ReStructuredTextHelper
|
|
8
|
+
def self.code_block(source, language)
|
|
9
|
+
buffer = StringIO.new
|
|
10
|
+
buffer.puts ".. code-block:: #{language}"
|
|
11
|
+
indented_source = source.lines.map do|line|
|
|
12
|
+
" #{line}"
|
|
13
|
+
end.join("\n")
|
|
14
|
+
buffer.puts indented_source
|
|
15
|
+
buffer.string
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
class MarkdownHelper
|
|
19
|
+
def self.code_block(source, language)
|
|
20
|
+
buffer = StringIO.new
|
|
21
|
+
buffer.puts "```#{language}"
|
|
22
|
+
buffer.puts source
|
|
23
|
+
buffer.puts '```'
|
|
24
|
+
buffer.string
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
def initialize(*args)
|
|
28
|
+
@segmenter = Polytrix::Documentation::CodeSegmenter.new
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def source
|
|
33
|
+
File.read source_file
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def code_block(source_code, language, opts = { format: :markdown })
|
|
37
|
+
case opts[:format]
|
|
38
|
+
when :rst
|
|
39
|
+
ReStructuredTextHelper.code_block source_code, language
|
|
40
|
+
when :markdown
|
|
41
|
+
MarkdownHelper.code_block source_code, language
|
|
42
|
+
else
|
|
43
|
+
fail IllegalArgumentError, "Unknown format: #{format}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Loses proper indentation on comments
|
|
48
|
+
def snippet_after(matcher)
|
|
49
|
+
segments = @segmenter.segment(source)
|
|
50
|
+
buffer = StringIO.new
|
|
51
|
+
segment = segments.find do |s|
|
|
52
|
+
doc_segment_content = s.first.join
|
|
53
|
+
doc_segment_content.match matcher
|
|
54
|
+
end
|
|
55
|
+
buffer.print segment[1].join "\n" if segment # return code segment
|
|
56
|
+
buffer.string
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def snippet_between(before_matcher, after_matcher)
|
|
60
|
+
segments = @segmenter.segment(source)
|
|
61
|
+
start_segment = find_segment_index segments, before_matcher
|
|
62
|
+
end_segment = find_segment_index segments, after_matcher
|
|
63
|
+
buffer = StringIO.new
|
|
64
|
+
if start_segment && end_segment
|
|
65
|
+
segments[start_segment...end_segment].each do |segment|
|
|
66
|
+
buffer.puts @segmenter.comment(segment[0]) unless segment == segments[start_segment]
|
|
67
|
+
buffer.puts segment[1].join
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
buffer.puts "\n"
|
|
71
|
+
buffer.string
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def find_segment_index(segments, matcher)
|
|
77
|
+
segments.find_index do |s|
|
|
78
|
+
doc_segment_content = s.first.join
|
|
79
|
+
doc_segment_content.match matcher
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Polytrix
|
|
2
|
+
module Documentation
|
|
3
|
+
module ViewHelper
|
|
4
|
+
def polytrix_toc
|
|
5
|
+
buffer = StringIO.new
|
|
6
|
+
buffer.puts '<ul>'
|
|
7
|
+
Polytrix.manifest.suites.each do |suite_name, suite|
|
|
8
|
+
buffer.puts "<li>#{suite_name}</li>"
|
|
9
|
+
buffer.puts '<ul>'
|
|
10
|
+
suite.samples.each do |challenge_name|
|
|
11
|
+
buffer.puts "<li>#{challenge_name}</li>"
|
|
12
|
+
end
|
|
13
|
+
buffer.puts '</ul>'
|
|
14
|
+
end
|
|
15
|
+
buffer.puts '</ul>'
|
|
16
|
+
|
|
17
|
+
buffer.string
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,18 +1,67 @@
|
|
|
1
|
+
require 'tilt' # seems to be a bug where padrino-helpers should require tilt
|
|
2
|
+
require 'padrino-helpers'
|
|
3
|
+
|
|
1
4
|
module Polytrix
|
|
2
5
|
class DocumentationGenerator
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
[
|
|
7
|
+
Padrino::Helpers::OutputHelpers,
|
|
8
|
+
Padrino::Helpers::AssetTagHelpers,
|
|
9
|
+
Padrino::Helpers::TagHelpers,
|
|
10
|
+
Polytrix::Documentation::Helpers::CodeHelper
|
|
11
|
+
].each do | helper|
|
|
12
|
+
include helper
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :scenario
|
|
16
|
+
|
|
17
|
+
def initialize(template_file = nil, scenario = nil)
|
|
18
|
+
@scenario = scenario
|
|
19
|
+
@template_file = template_file
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def process(challenges)
|
|
23
|
+
@challenges = challenges
|
|
24
|
+
if File.readable? @template_file
|
|
25
|
+
# @template_file ||= find_file @search_path, scenario, ""
|
|
26
|
+
erb = ERB.new File.read(@template_file)
|
|
27
|
+
@result = erb.result(binding) || ''
|
|
28
|
+
end
|
|
29
|
+
end
|
|
5
30
|
|
|
6
|
-
def
|
|
7
|
-
|
|
31
|
+
def save(target_file)
|
|
32
|
+
fail 'No results to write, please call process before save' if @result.nil?
|
|
33
|
+
if @result.empty?
|
|
34
|
+
# Warn: skip creating empty file
|
|
35
|
+
else
|
|
36
|
+
FileUtils.mkdir_p File.dirname(target_file)
|
|
37
|
+
File.open(target_file, 'wb') do |f|
|
|
38
|
+
f.write @result
|
|
39
|
+
end
|
|
40
|
+
end
|
|
8
41
|
end
|
|
9
42
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
def code2doc(source_file, language)
|
|
44
|
+
source_code = File.read(source_file)
|
|
45
|
+
if language.nil?
|
|
46
|
+
language, comment_style = Documentation::CommentStyles.infer File.extname(source_file)
|
|
47
|
+
segmenter_language = comment_style[:language] || language
|
|
48
|
+
else
|
|
49
|
+
segmenter_language = language
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
buffer = StringIO.new
|
|
53
|
+
segmenter_options = {
|
|
54
|
+
language: segmenter_language
|
|
55
|
+
}
|
|
56
|
+
segmenter = Polytrix::Documentation::CodeSegmenter.new(segmenter_options)
|
|
57
|
+
segments = segmenter.segment source_code
|
|
58
|
+
segments.each do |comment, code|
|
|
59
|
+
comment = comment.join("\n")
|
|
60
|
+
code = code.join("\n")
|
|
61
|
+
buffer.puts comment unless comment.empty?
|
|
62
|
+
buffer.puts code_block code, language unless code.empty?
|
|
63
|
+
end
|
|
64
|
+
buffer.string
|
|
16
65
|
end
|
|
17
66
|
end
|
|
18
67
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'hashie/dash'
|
|
2
|
+
require 'thor'
|
|
3
|
+
|
|
4
|
+
module Polytrix
|
|
5
|
+
module Executor
|
|
6
|
+
class OutputDecorator
|
|
7
|
+
# Reserve :red, :black, :white
|
|
8
|
+
COLORS = [:green, :yellow, :blue, :magenta, :cyan]
|
|
9
|
+
|
|
10
|
+
def self.next_color
|
|
11
|
+
@next_color ||= 0
|
|
12
|
+
@next_color += 1
|
|
13
|
+
COLORS[@next_color % COLORS.size]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(real_io, prefix = nil)
|
|
17
|
+
@real_io = real_io
|
|
18
|
+
# @prefix = set_color(prefix, :cyan)
|
|
19
|
+
@prefix = "#{prefix}: " if prefix
|
|
20
|
+
@color = self.class.next_color
|
|
21
|
+
@thor_shell = Thor::Shell::Color.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def puts(line)
|
|
25
|
+
line = line.gsub(/^/, @prefix) if @prefix
|
|
26
|
+
@real_io.puts @thor_shell.set_color(line, @color)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def <<(line)
|
|
30
|
+
line = line.gsub(/^/, @prefix) if @prefix
|
|
31
|
+
@real_io << @thor_shell.set_color(line, @color)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def method_missing(meth, *args, &block)
|
|
35
|
+
@real_io.send meth, *args, &block
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class ExecutionError < StandardError
|
|
40
|
+
attr_accessor :execution_result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class ExecutionResult < Hashie::Dash
|
|
44
|
+
property :exitstatus, require: true
|
|
45
|
+
property :stdout, required: true
|
|
46
|
+
property :stderr, required: true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class InteractiveExecutor
|
|
50
|
+
def execute(command, opts)
|
|
51
|
+
system command
|
|
52
|
+
# FIXME: This needs to return execution result, if interactive remains supported
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class ShellOutExecutor
|
|
57
|
+
def execute(command, opts)
|
|
58
|
+
prefix = opts.delete :prefix
|
|
59
|
+
shell = Mixlib::ShellOut.new(command, opts)
|
|
60
|
+
shell.live_stream = OutputDecorator.new($stdout, prefix) unless Polytrix.configuration.suppress_output
|
|
61
|
+
shell.run_command
|
|
62
|
+
execution_result = ExecutionResult.new exitstatus: shell.exitstatus, stdout: shell.stdout, stderr: shell.stderr
|
|
63
|
+
begin
|
|
64
|
+
shell.error!
|
|
65
|
+
rescue Mixlib::ShellOut::ShellCommandFailed => e
|
|
66
|
+
execution_error = ExecutionError.new(e)
|
|
67
|
+
execution_error.execution_result = execution_result
|
|
68
|
+
raise execution_error
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
execution_result
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_writer :executor
|
|
76
|
+
|
|
77
|
+
def executor
|
|
78
|
+
@executor ||= if ENV['INTERACTIVE']
|
|
79
|
+
InteractiveExecutor.new
|
|
80
|
+
else
|
|
81
|
+
ShellOutExecutor.new
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def execute(command, opts = {})
|
|
86
|
+
executor.execute(command, opts)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Polytrix
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def logger
|
|
6
|
+
@logger ||= Polytrix.configuration.logger
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Logger
|
|
11
|
+
include ClassMethods
|
|
12
|
+
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.extend(ClassMethods)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/polytrix/manifest.rb
CHANGED
|
@@ -2,13 +2,44 @@ require 'yaml'
|
|
|
2
2
|
require 'hashie/dash'
|
|
3
3
|
require 'hashie/mash'
|
|
4
4
|
require 'hashie/extensions/coercion'
|
|
5
|
+
require 'hashie/extensions/deep_merge'
|
|
5
6
|
|
|
6
7
|
module Polytrix
|
|
8
|
+
# Polytrix::Manifest acts as a test manifest. It defines the test scenarios that should be run,
|
|
9
|
+
# and may be shared across multiple implementors when used for a compliance suite.
|
|
10
|
+
#
|
|
11
|
+
# A manifest is generally defined and loaded from YAML. Here's an example manifest:
|
|
12
|
+
# ---
|
|
13
|
+
# global_env:
|
|
14
|
+
# LOCALE: <%= ENV['LANG'] %>
|
|
15
|
+
# FAVORITE_NUMBER: 5
|
|
16
|
+
# suites:
|
|
17
|
+
# Katas:
|
|
18
|
+
# env:
|
|
19
|
+
# NAME: 'Max'
|
|
20
|
+
# samples:
|
|
21
|
+
# - hello world
|
|
22
|
+
# - quine
|
|
23
|
+
# Tutorials:
|
|
24
|
+
# env:
|
|
25
|
+
# samples:
|
|
26
|
+
# - deploying
|
|
27
|
+
# - documenting
|
|
28
|
+
#
|
|
29
|
+
# The *suites* object defines the tests. Each object, under suites, like *Katas* or *Tutorials* in this
|
|
30
|
+
# example, represents a test suite. A test suite is subdivided into *samples*, that each act as a scenario.
|
|
31
|
+
# The *global_env* object and the *env* under each suite define (and standardize) the input for each test.
|
|
32
|
+
# The *global_env* values will be made available to all tests as environment variables, along with the *env*
|
|
33
|
+
# values for that specific test.
|
|
34
|
+
#
|
|
7
35
|
class Manifest < Hashie::Dash
|
|
36
|
+
include Logger
|
|
37
|
+
include Hashie::Extensions::DeepMerge
|
|
38
|
+
|
|
8
39
|
class Environment < Hashie::Mash
|
|
9
|
-
# Hashie Coercion - automatically treat all values as
|
|
40
|
+
# Hashie Coercion - automatically treat all values as String
|
|
10
41
|
def self.coerce(obj)
|
|
11
|
-
data = obj.
|
|
42
|
+
data = obj.reduce({}) do |h, (key, value)|
|
|
12
43
|
h[key] = value.to_s
|
|
13
44
|
h
|
|
14
45
|
end
|
|
@@ -16,13 +47,17 @@ module Polytrix
|
|
|
16
47
|
end
|
|
17
48
|
end
|
|
18
49
|
|
|
19
|
-
class Suite < Hashie::
|
|
50
|
+
class Suite < Hashie::Dash
|
|
51
|
+
include Hashie::Extensions::Coercion
|
|
52
|
+
property :env, default: {}
|
|
53
|
+
property :samples, default: []
|
|
54
|
+
property :results
|
|
20
55
|
end
|
|
21
56
|
|
|
22
57
|
class Suites < Hashie::Mash
|
|
23
|
-
# Hashie Coercion - automatically treat all values as
|
|
58
|
+
# Hashie Coercion - automatically treat all values as Suite
|
|
24
59
|
def self.coerce(obj)
|
|
25
|
-
data = obj.
|
|
60
|
+
data = obj.reduce({}) do |h, (key, value)|
|
|
26
61
|
h[key] = Polytrix::Manifest::Suite.new(value)
|
|
27
62
|
h
|
|
28
63
|
end
|
|
@@ -36,11 +71,33 @@ module Polytrix
|
|
|
36
71
|
property :suites
|
|
37
72
|
coerce_key :suites, Polytrix::Manifest::Suites
|
|
38
73
|
|
|
74
|
+
# Parses a YAML file to create a {Manifest} object.
|
|
39
75
|
def self.from_yaml(yaml_file)
|
|
76
|
+
ENV['POLYTRIX_SEED'] ||= $PROCESS_ID.to_s
|
|
77
|
+
logger.debug "Loading #{yaml_file}"
|
|
40
78
|
raw_content = File.read(yaml_file)
|
|
41
79
|
processed_content = ERB.new(raw_content).result
|
|
42
|
-
data = YAML
|
|
80
|
+
data = YAML.load processed_content
|
|
43
81
|
new data
|
|
44
82
|
end
|
|
83
|
+
|
|
84
|
+
def find_suite(suite_name)
|
|
85
|
+
_, suite = suites.find { |name, _| name.downcase == suite_name.downcase }
|
|
86
|
+
suite
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def find_challenge(suite_name, scenario_name)
|
|
90
|
+
suite = find_suite suite_name
|
|
91
|
+
return nil if suite.nil?
|
|
92
|
+
|
|
93
|
+
if suite.samples.is_a? Array
|
|
94
|
+
# No results yet
|
|
95
|
+
suite.samples.find { |name, _| name.downcase == scenario_name.downcase }
|
|
96
|
+
Challenge.new suite: suite_name, name: scenario_name
|
|
97
|
+
else
|
|
98
|
+
_, challenge_data = find_suite('identity').samples.find { |name, challenge| name.downcase == scenario_name.downcase }
|
|
99
|
+
Challenge.new(suite: suite_name, name: scenario_name, result: challenge_data)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
45
102
|
end
|
|
46
|
-
end
|
|
103
|
+
end
|
data/lib/polytrix/result.rb
CHANGED
|
@@ -2,8 +2,22 @@ require 'hashie/dash'
|
|
|
2
2
|
|
|
3
3
|
module Polytrix
|
|
4
4
|
class Result < Hashie::Dash
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
extend Forwardable
|
|
6
|
+
include Hashie::Extensions::Coercion
|
|
7
|
+
property :execution_result # , required: true
|
|
8
|
+
def_delegators :execution_result, :stdout, :stderr, :exitstatus
|
|
9
|
+
property :source_file # , required: true
|
|
7
10
|
property :data
|
|
11
|
+
property :validations, default: Validations.new
|
|
12
|
+
coerce_key :validations, Validations
|
|
13
|
+
|
|
14
|
+
def status
|
|
15
|
+
# A feature can be validated by different suites, or manually vs an automated suite.
|
|
16
|
+
# That's why there's a precedence rather than boolean algebra here...
|
|
17
|
+
return 'failed' if validations.any? { |v| v.result == 'failed' }
|
|
18
|
+
return 'passed' if validations.any? { |v| v.result == 'passed' }
|
|
19
|
+
return 'pending' if validations.any? { |v| v.result == 'pending' }
|
|
20
|
+
'skipped'
|
|
21
|
+
end
|
|
8
22
|
end
|
|
9
23
|
end
|
|
@@ -6,36 +6,61 @@ require 'fileutils'
|
|
|
6
6
|
module Polytrix
|
|
7
7
|
module RSpec
|
|
8
8
|
class DocumentationFormatter < ::RSpec::Core::Formatters::BaseFormatter
|
|
9
|
+
include Polytrix::Core::FileSystemHelper
|
|
10
|
+
|
|
9
11
|
def initialize(output)
|
|
12
|
+
@templates_dir = 'doc-src'
|
|
13
|
+
@output_dir = 'docs'
|
|
10
14
|
@results = Hashie::Mash.new
|
|
15
|
+
@summary_files = %w(index)
|
|
11
16
|
super
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def example_group_finished(example_group)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
polytrix_challenges = example_group.examples.map { |e| e.metadata[:polytrix_challenge] }
|
|
21
|
+
target_file = target_file_for example_group
|
|
22
|
+
template_file = template_for example_group
|
|
23
|
+
produce_doc template_file, target_file, example_group.description, polytrix_challenges if template_file
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def dump_summary(duration, example_count, failure_count, pending_count)
|
|
21
|
-
|
|
22
|
-
all_challenges = examples.map{|e| e.metadata[:polytrix]}
|
|
27
|
+
all_challenges = examples.map { |e| e.metadata[:polytrix_challenge] }
|
|
23
28
|
grouped_challenges = all_challenges.compact.group_by(&:name)
|
|
24
|
-
|
|
29
|
+
@summary_files.each do |summary_file|
|
|
30
|
+
template_file = template_for summary_file, use_default: false
|
|
31
|
+
next if template_file.nil?
|
|
32
|
+
target_file = target_file_for_summary(template_file)
|
|
33
|
+
produce_doc template_file, target_file, 'Summary', grouped_challenges
|
|
34
|
+
end
|
|
25
35
|
end
|
|
26
36
|
|
|
27
37
|
private
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
f.write doc
|
|
36
|
-
end
|
|
38
|
+
|
|
39
|
+
def template_for(name, opts = { use_default: true })
|
|
40
|
+
name = name.description if name.respond_to? :description
|
|
41
|
+
begin
|
|
42
|
+
find_file @templates_dir, name, ''
|
|
43
|
+
rescue Polytrix::Core::FileSystemHelper::FileNotFound
|
|
44
|
+
Polytrix.configuration.default_doc_template if opts[:use_default] == true
|
|
37
45
|
end
|
|
38
46
|
end
|
|
47
|
+
|
|
48
|
+
def target_file_for(example_group)
|
|
49
|
+
names = [@output_dir].concat(example_group.parent_groups.reverse.map(&:description))
|
|
50
|
+
# Markdown format by default, but will be overridden to match the template
|
|
51
|
+
slugify(names.join File::SEPARATOR) + '.md'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def target_file_for_summary(template_file)
|
|
55
|
+
name = File.basename(template_file)
|
|
56
|
+
slugify("docs/#{name}")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def produce_doc(template_file, target_file, scenario, data)
|
|
60
|
+
doc_gen = Polytrix::DocumentationGenerator.new template_file, scenario
|
|
61
|
+
doc_gen.process data
|
|
62
|
+
doc_gen.save target_file
|
|
63
|
+
end
|
|
39
64
|
end
|
|
40
65
|
end
|
|
41
|
-
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'polytrix/rspec'
|
|
2
|
+
require 'hashie/mash'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Polytrix
|
|
7
|
+
module RSpec
|
|
8
|
+
class YAMLReport < ::RSpec::Core::Formatters::BaseFormatter
|
|
9
|
+
def example_passed(example)
|
|
10
|
+
add_implementation_result example, :passed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def example_failed(example)
|
|
14
|
+
add_implementation_result example, :failed
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def example_pending(example)
|
|
18
|
+
add_implementation_result example, :pending
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
|
22
|
+
results = Hashie::Mash.new(Polytrix.manifest.dup.to_hash)
|
|
23
|
+
all_challenges = examples.map { |e| e.metadata[:polytrix_challenge] }
|
|
24
|
+
grouped_challenges = all_challenges.compact.group_by(&:name)
|
|
25
|
+
results.suites.each do |suite_name, suite|
|
|
26
|
+
suite.samples = suite.samples.each_with_object({}) do |sample_name, sample_results|
|
|
27
|
+
sample_results[sample_name] ||= {}
|
|
28
|
+
if grouped_challenges[sample_name]
|
|
29
|
+
challenge_results = grouped_challenges[sample_name]
|
|
30
|
+
challenge_results.each do |challenge|
|
|
31
|
+
sample_results[sample_name][challenge.implementor.name] = challenge.result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
@output.puts YAML.dump(results.to_hash)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def add_implementation_result(example, state)
|
|
42
|
+
validation = Validation.new(
|
|
43
|
+
validated_by: 'polytrix',
|
|
44
|
+
result: state.to_s
|
|
45
|
+
)
|
|
46
|
+
challenge = example.metadata[:polytrix_challenge]
|
|
47
|
+
challenge.result.validations << validation unless challenge.nil? || challenge.result.nil?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/polytrix/rspec.rb
CHANGED
|
@@ -9,67 +9,46 @@ end
|
|
|
9
9
|
|
|
10
10
|
module Polytrix
|
|
11
11
|
module RSpec
|
|
12
|
-
module Helper
|
|
13
|
-
def challenge_runner
|
|
14
|
-
@challenge_runner ||= Polytrix::ChallengeRunner.createRunner
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def execute_challenge(sdk_dir, challenge_name, vars)
|
|
18
|
-
implementor_name = File.basename(sdk_dir) # Might not be a good assumption
|
|
19
|
-
implementor = Polytrix.implementors.find { |i| i.name == implementor_name }
|
|
20
|
-
challenge = ChallengeBuilder.new(implementor).build :name => challenge_name, :vars => vars, :basedir => sdk_dir, :implementor => implementor.name
|
|
21
|
-
example.metadata[:polytrix] = challenge
|
|
22
|
-
result = challenge.run
|
|
23
|
-
yield result
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
12
|
class << self
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
def shared_examples(caller) # rubocop:disable MethodLength
|
|
14
|
+
# FIXME: Long method because it's hard to eval in the right context
|
|
15
|
+
caller.instance_eval do
|
|
16
|
+
Polytrix.manifest['suites'].each do |suite_name, suite_config|
|
|
17
|
+
describe suite_name do
|
|
18
|
+
samples = suite_config['samples'] || []
|
|
19
|
+
samples.each do |scenario|
|
|
20
|
+
describe scenario do
|
|
21
|
+
Polytrix.implementors.each do |sdk|
|
|
22
|
+
it sdk.name, sdk.name.to_sym => true do
|
|
23
|
+
begin
|
|
24
|
+
skip "#{sdk.name} is not setup" unless File.directory? sdk.basedir
|
|
25
|
+
challenge = sdk.build_challenge suite: suite_name, name: scenario, vars: suite_config['env']
|
|
26
|
+
example.metadata[:polytrix_challenge] = challenge
|
|
27
|
+
challenge.run
|
|
28
|
+
validators = Polytrix::ValidatorRegistry.validators_for challenge
|
|
29
|
+
validators.each do |validator|
|
|
30
|
+
instance_exec challenge, &validator.callback
|
|
31
|
+
end
|
|
32
|
+
rescue Polytrix::FeatureNotImplementedError => e
|
|
33
|
+
skip e.message
|
|
34
|
+
rescue ThreadError => e
|
|
35
|
+
# Extra debug info for ThreadError
|
|
36
|
+
$stderr.puts "ThreadError detected: #{e.message}"
|
|
37
|
+
$stderr.puts "ThreadError backtrace: #{e.backtrace}"
|
|
38
|
+
fail e
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
36
45
|
end
|
|
37
46
|
end
|
|
38
47
|
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def code_sample(challenge, description = '', environment = [], services = [], &block)
|
|
44
|
-
challenge_file = challenge.downcase.gsub(' ', '_')
|
|
45
|
-
describe challenge, markdown: description,
|
|
46
|
-
# :environment => redact(environment),
|
|
47
|
-
services: services do
|
|
48
|
-
Polytrix.implementors.each do |sdk|
|
|
49
|
-
sdk = sdk.name if sdk.respond_to? :name
|
|
50
|
-
it sdk, sdk.to_sym => true, 'data-challenge' => challenge_file, 'data-sdk' => sdk do
|
|
51
|
-
Polytrix.results.example_started example
|
|
52
|
-
begin
|
|
53
|
-
sdk_dir = Polytrix.sdk_dir sdk
|
|
54
|
-
pending "#{sdk} is not setup" unless File.directory? sdk_dir
|
|
55
|
-
challenge_runner.find_challenge! challenge_file, sdk_dir
|
|
56
|
-
execute_challenge sdk_dir, challenge_file, environment do |result|
|
|
57
|
-
Polytrix.results.execution_result example, result
|
|
58
|
-
instance_exec result, &block
|
|
59
|
-
end
|
|
60
48
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
rescue ThreadError => e
|
|
64
|
-
puts "ThreadError detected: #{e.message}"
|
|
65
|
-
puts "ThreadError backtrace: #{e.backtrace}"
|
|
66
|
-
raise e
|
|
67
|
-
end
|
|
49
|
+
def run_manifest(manifest)
|
|
50
|
+
shared_examples(self)
|
|
68
51
|
end
|
|
69
52
|
end
|
|
70
53
|
end
|
|
71
54
|
end
|
|
72
|
-
|
|
73
|
-
RSpec.configure do |c|
|
|
74
|
-
c.include Polytrix::RSpec::Helper
|
|
75
|
-
end
|
|
@@ -2,6 +2,7 @@ module Polytrix
|
|
|
2
2
|
module Runners
|
|
3
3
|
module Middleware
|
|
4
4
|
class FeatureExecutor
|
|
5
|
+
include Polytrix::Core::FileSystemHelper
|
|
5
6
|
def initialize(app)
|
|
6
7
|
@app = app
|
|
7
8
|
end
|
|
@@ -10,10 +11,10 @@ module Polytrix
|
|
|
10
11
|
challenge_runner = env[:challenge_runner]
|
|
11
12
|
env_file = env[:env_file]
|
|
12
13
|
source_file = env[:source_file]
|
|
13
|
-
relative_source_file = source_file
|
|
14
|
+
relative_source_file = relativize(source_file, env[:basedir])
|
|
14
15
|
command = challenge_runner.challenge_command(env_file, relative_source_file)
|
|
15
|
-
|
|
16
|
-
env[:result] = Result.new(
|
|
16
|
+
execution_result = challenge_runner.run_command command
|
|
17
|
+
env[:result] = Result.new(execution_result: execution_result, source_file: env[:source_file].to_s)
|
|
17
18
|
@app.call env
|
|
18
19
|
env[:result]
|
|
19
20
|
end
|