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
@@ -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
|