omnitest 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.gitmodules +0 -0
  4. data/.groc.json +7 -0
  5. data/.rspec +6 -0
  6. data/.rubocop.yml +5 -0
  7. data/.rubocop_todo.yml +47 -0
  8. data/.travis.yml +12 -0
  9. data/.yardopts +3 -0
  10. data/Gemfile +26 -0
  11. data/README.md +341 -0
  12. data/Rakefile +33 -0
  13. data/appveyor.yml +9 -0
  14. data/bin/omnidoc +5 -0
  15. data/bin/omnitask +5 -0
  16. data/bin/omnitest +5 -0
  17. data/bower.json +21 -0
  18. data/doc-src/index.md.tt +341 -0
  19. data/doc-src/project_sets.md.tt +31 -0
  20. data/doc-src/usage/crosstask.md.tt +86 -0
  21. data/doc-src/usage/omnitest.md.tt +87 -0
  22. data/features/bootstrapping.feature +25 -0
  23. data/features/cloning.feature +32 -0
  24. data/features/fixtures/configs/omnitest_sample.yaml +11 -0
  25. data/features/fixtures/configs/skeptic_empty.yaml +12 -0
  26. data/features/fixtures/configs/skeptic_hello_world.yaml +10 -0
  27. data/features/show.feature +38 -0
  28. data/features/states.feature +40 -0
  29. data/features/step_definitions/sdk_steps.rb +22 -0
  30. data/features/support/env.rb +9 -0
  31. data/lib/omnitest.rb +211 -0
  32. data/lib/omnitest/cli.rb +297 -0
  33. data/lib/omnitest/command.rb +103 -0
  34. data/lib/omnitest/command/generate.rb +29 -0
  35. data/lib/omnitest/command/generators/code2doc.rb +79 -0
  36. data/lib/omnitest/command/generators/dashboard.rb +148 -0
  37. data/lib/omnitest/command/generators/documentation.rb +119 -0
  38. data/lib/omnitest/command/list.rb +62 -0
  39. data/lib/omnitest/command/project_action.rb +26 -0
  40. data/lib/omnitest/command/scenario_action.rb +20 -0
  41. data/lib/omnitest/command/show.rb +148 -0
  42. data/lib/omnitest/command/task.rb +27 -0
  43. data/lib/omnitest/command/test.rb +41 -0
  44. data/lib/omnitest/configuration.rb +53 -0
  45. data/lib/omnitest/documentation_generator.rb +68 -0
  46. data/lib/omnitest/project.rb +100 -0
  47. data/lib/omnitest/project_logger.rb +273 -0
  48. data/lib/omnitest/project_set.rb +47 -0
  49. data/lib/omnitest/reporters.rb +27 -0
  50. data/lib/omnitest/reporters/hash_reporter.rb +32 -0
  51. data/lib/omnitest/reporters/json_reporter.rb +12 -0
  52. data/lib/omnitest/reporters/markdown_reporter.rb +26 -0
  53. data/lib/omnitest/reporters/yaml_reporter.rb +12 -0
  54. data/lib/omnitest/run_action.rb +44 -0
  55. data/lib/omnitest/version.rb +3 -0
  56. data/lib/omnitest/workflow.rb +5 -0
  57. data/mkdocs.yml +8 -0
  58. data/omnitest.gemspec +39 -0
  59. data/omnitest.yaml +5 -0
  60. data/resources/assets/angular/angular.min.js +217 -0
  61. data/resources/assets/angular/angular.min.js.map +8 -0
  62. data/resources/assets/angular/json-formatter.min.css +6 -0
  63. data/resources/assets/angular/json-formatter.min.js +7 -0
  64. data/resources/assets/angular/ng-table.map +1 -0
  65. data/resources/assets/angular/ng-table.min.css +3 -0
  66. data/resources/assets/angular/ng-table.min.js +3 -0
  67. data/resources/assets/angular/ui-bootstrap-tpls.min.js +10 -0
  68. data/resources/assets/bootstrap/bootstrap.min.css +9 -0
  69. data/resources/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  70. data/resources/assets/fonts/glyphicons-halflings-regular.svg +229 -0
  71. data/resources/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  72. data/resources/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  73. data/resources/assets/pygments/autumn.css +58 -0
  74. data/resources/assets/pygments/borland.css +46 -0
  75. data/resources/assets/pygments/bw.css +34 -0
  76. data/resources/assets/pygments/colorful.css +61 -0
  77. data/resources/assets/pygments/default.css +62 -0
  78. data/resources/assets/pygments/emacs.css +61 -0
  79. data/resources/assets/pygments/friendly.css +61 -0
  80. data/resources/assets/pygments/fruity.css +69 -0
  81. data/resources/assets/pygments/github.css +61 -0
  82. data/resources/assets/pygments/manni.css +61 -0
  83. data/resources/assets/pygments/monokai.css +64 -0
  84. data/resources/assets/pygments/murphy.css +61 -0
  85. data/resources/assets/pygments/native.css +69 -0
  86. data/resources/assets/pygments/pastie.css +60 -0
  87. data/resources/assets/pygments/perldoc.css +58 -0
  88. data/resources/assets/pygments/tango.css +69 -0
  89. data/resources/assets/pygments/trac.css +59 -0
  90. data/resources/assets/pygments/vim.css +69 -0
  91. data/resources/assets/pygments/vs.css +33 -0
  92. data/resources/assets/pygments/zenburn.css +1 -0
  93. data/resources/assets/style.css +56 -0
  94. data/resources/code_sample.tt +2 -0
  95. data/resources/generators/dashboard/files/dashboard.html.tt +51 -0
  96. data/resources/generators/dashboard/files/dashboard.js +26 -0
  97. data/resources/generators/dashboard/templates/_test_report.html.haml +91 -0
  98. data/resources/generators/todo/templates/todo.md.tt +6 -0
  99. data/resources/generators/todo/todo_template.rb +1 -0
  100. data/samples/.gitignore +2 -0
  101. data/samples/_markdown.md +5 -0
  102. data/samples/bootstrap.sh +2 -0
  103. data/samples/clone.sh +2 -0
  104. data/samples/code2doc.sh +5 -0
  105. data/samples/default_bootstrap.rb +7 -0
  106. data/samples/detect.sh +2 -0
  107. data/samples/exec.sh +2 -0
  108. data/samples/omnitest.yaml +24 -0
  109. data/samples/omnitest_simple.yaml +8 -0
  110. data/samples/scripts/bootstrap +3 -0
  111. data/samples/show.sh +4 -0
  112. data/samples/skeptic.yaml +13 -0
  113. data/samples/skeptic_simple.yaml +9 -0
  114. data/samples/test.sh +2 -0
  115. data/samples/tests/omnitest/validators.rb +23 -0
  116. data/samples/verify.sh +3 -0
  117. data/scripts/bootstrap.ps1 +7 -0
  118. data/scripts/run_script.sh +4 -0
  119. data/skeptic.yaml +26 -0
  120. data/spec/fabricators/project_fabricator.rb +19 -0
  121. data/spec/fabricators/scenario_fabricator.rb +6 -0
  122. data/spec/fabricators/test_manifest_fabricator.rb +41 -0
  123. data/spec/fabricators/validator_fabricator.rb +12 -0
  124. data/spec/fixtures/factorial.py +18 -0
  125. data/spec/fixtures/omnitest.yaml +11 -0
  126. data/spec/fixtures/skeptic.yaml +16 -0
  127. data/spec/fixtures/src-doc/_scenario.md.erb +1 -0
  128. data/spec/fixtures/src-doc/quine.md.erb +20 -0
  129. data/spec/omnitest/cli_spec.rb +38 -0
  130. data/spec/omnitest/configuration_spec.rb +25 -0
  131. data/spec/omnitest/documentation_generator_spec.rb +59 -0
  132. data/spec/omnitest/file_finder_spec.rb +21 -0
  133. data/spec/omnitest/project_spec.rb +65 -0
  134. data/spec/omnitest_spec.rb +13 -0
  135. data/spec/spec_helper.rb +32 -0
  136. data/spec/thor_spy.rb +66 -0
  137. data/tests/omnitest/bootstrap_validations.rb +7 -0
  138. data/tests/omnitest/show_validations.rb +22 -0
  139. data/yard_macros.rb +25 -0
  140. metadata +470 -0
@@ -0,0 +1,148 @@
1
+ require 'json'
2
+ require 'tilt'
3
+ require 'haml'
4
+ require 'omnitest/reporters'
5
+
6
+ module Omnitest
7
+ module Command
8
+ class Generate
9
+ class Dashboard < Thor::Group
10
+ include Thor::Actions
11
+ include Omnitest::Core::FileSystem
12
+ module Helpers
13
+ include Omnitest::Core::Util::String
14
+ # include Padrino::Helpers::RenderHelpers # requires sinatra-compatible render method
15
+ include Padrino::Helpers::TagHelpers
16
+ include Padrino::Helpers::OutputHelpers
17
+ include Padrino::Helpers::AssetTagHelpers
18
+
19
+ def projects
20
+ Omnitest.projects.map do |project|
21
+ slugify(project.name)
22
+ end
23
+ end
24
+
25
+ def results
26
+ rows = []
27
+ grouped_scenarios = Omnitest.scenarios.group_by { |scenario| [scenario.suite, scenario.name] }
28
+ grouped_scenarios.each do |(suite, name), scenarios|
29
+ row = {
30
+ slug_prefix: slugify(suite, name),
31
+ suite: suite,
32
+ scenario: name
33
+ }
34
+ Omnitest.projects.each do |project|
35
+ scenario = scenarios.find { |s| s.psychic.name == project.name }
36
+ row[slugify(project.name)] = scenario.status_description
37
+ end
38
+ rows << row
39
+ end
40
+ rows
41
+ end
42
+
43
+ def as_json(data)
44
+ JSON.dump(data)
45
+ rescue => e
46
+ JSON.dump(to_utf(data))
47
+ end
48
+
49
+ def to_utf(data)
50
+ Hash[
51
+ data.collect do |k, v|
52
+ if v.respond_to?(:collect)
53
+ [k, to_utf(v)]
54
+ elsif v.respond_to?(:encoding)
55
+ [k, v.dup.encode('UTF-8')]
56
+ else
57
+ [k, v]
58
+ end
59
+ end
60
+ ]
61
+ end
62
+
63
+ def status(status, msg = nil, _color = :cyan)
64
+ "<strong>#{status}</strong> <em>#{msg}</em>"
65
+ end
66
+
67
+ def bootstrap_color(color)
68
+ bootstrap_classes = {
69
+ green: 'success',
70
+ cyan: 'primary',
71
+ red: 'danger',
72
+ yellow: 'warning'
73
+ }
74
+ bootstrap_classes.key?(color) ? bootstrap_classes[color] : color
75
+ end
76
+ end
77
+
78
+ include Helpers
79
+
80
+ class_option :destination, default: 'reports/'
81
+ class_option :code_style, default: 'github'
82
+
83
+ def self.source_root
84
+ Omnitest::Reporters::GENERATORS_DIR
85
+ end
86
+
87
+ def report_name
88
+ @report_name ||= self.class.name.downcase.split('::').last
89
+ end
90
+
91
+ def add_framework_to_source_root
92
+ source_paths.map do | path |
93
+ path << "/#{report_name}"
94
+ end
95
+ end
96
+
97
+ def set_destination_root
98
+ self.destination_root = options[:destination]
99
+ end
100
+
101
+ def setup
102
+ @tabs = {}
103
+ @tabs['Dashboard'] = 'dashboard.html'
104
+ Omnitest.update_config!(options)
105
+ Omnitest.setup
106
+ end
107
+
108
+ def create_spy_reports
109
+ reports = Omnitest::Skeptic::Spies.reports[:dashboard]
110
+ reports.each do | report_class |
111
+ if report_class.respond_to? :tab_name
112
+ @active_tab = report_class.tab_name
113
+ @tabs[@active_tab] = report_class.tab_target
114
+ else
115
+ @active_tab = nil
116
+ end
117
+ report_class.tabs = @tabs
118
+ invoke report_class, args, options
119
+ end if reports
120
+ end
121
+
122
+ def copy_assets
123
+ directory Omnitest::Reporters::ASSETS_DIR, 'assets'
124
+ end
125
+
126
+ def copy_base_structure
127
+ @active_tab = 'Dashboard'
128
+ directory 'files', '.'
129
+ end
130
+
131
+ def create_results_json
132
+ create_file 'matrix.json', as_json(results)
133
+ end
134
+
135
+ def create_test_reports
136
+ template_file = find_in_source_paths('templates/_test_report.html.haml')
137
+ template = Tilt.new(template_file)
138
+ Omnitest.scenarios.each do |scenario|
139
+ @scenario = scenario
140
+ add_file "details/#{scenario.slug}.html" do
141
+ template.render(self)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,119 @@
1
+ require 'json'
2
+ require 'omnitest/reporters'
3
+
4
+ module Omnitest
5
+ module Command
6
+ class Generate
7
+ class Documentation < Thor::Group
8
+ include Thor::Actions
9
+ include Omnitest::Core::FileSystem
10
+ include Omnitest::Psychic::Code2Doc::CodeHelper
11
+ include Omnitest::Psychic::Code2Doc::SnippetHelper
12
+
13
+ BUILTIN_GENERATORS = Dir["#{Omnitest::Reporters::GENERATORS_DIR}/*"].select { |f| File.directory? f }
14
+
15
+ attr_reader :projects, :project, :project_name, :project_basedir
16
+
17
+ class << self
18
+ def generators
19
+ BUILTIN_GENERATORS + Dir['tests/omnitest/generators/*'].select { |f| File.directory? f }
20
+ end
21
+
22
+ def generator_names
23
+ generators.map { |d| File.basename d }
24
+ end
25
+
26
+ def generator_not_found(generator)
27
+ s = "ERROR: No generator named #{generator}, available generators: "
28
+ s << generator_names.join(', ')
29
+ end
30
+
31
+ def source_root
32
+ Omnitest::RESOURCES_DIR
33
+ end
34
+ end
35
+
36
+ argument :project_regexp, default: 'all'
37
+ argument :scenario_regexp, default: 'all'
38
+ class_option :source, default: 'doc-src/', desc: 'Source folder with documentation templates'
39
+ class_option :destination, default: 'docs/', desc: 'Destination for generated documentation'
40
+ class_option :template, desc: 'The name or location of a custom generator template'
41
+ class_option :scope, desc: 'Whether the template should be applied once (global), per project, or per scenario',
42
+ enum: %w(global project scenario), default: 'global'
43
+ class_option :failed, type: :boolean, desc: 'Only list tests that failed / passed'
44
+ class_option :skipped, type: :boolean, desc: 'Only list tests that were skipped / executed'
45
+ class_option :samples, type: :boolean, desc: 'Only list tests that have sample code / do not have sample code'
46
+ class_option :travis, type: :boolean, desc: "Enable/disable delegation to travis-build, if it's available"
47
+ def setup
48
+ Omnitest.update_config!(options)
49
+ Omnitest.setup
50
+ end
51
+
52
+ def set_source_and_destination
53
+ unless options[:template] || File.exist?(options[:source])
54
+ abort 'Either the --source directory must exist, or --template must be specified'
55
+ end
56
+
57
+ if options[:template]
58
+ generator = self.class.generators.find { |d| File.basename(d) == options[:template] }
59
+ abort self.class.generator_not_found(generator) if generator.nil?
60
+ source_paths << generator
61
+ else
62
+ source_paths << Pathname(options[:source]).expand_path
63
+ end
64
+
65
+ self.destination_root = options[:destination]
66
+ end
67
+
68
+ def apply_template
69
+ if options[:template]
70
+ generator_script = "#{options[:template]}_template.rb"
71
+ apply(generator_script)
72
+ else
73
+ case options[:scope]
74
+ when 'global'
75
+ @projects = Omnitest.filter_projects(project_regexp)
76
+ process_directory
77
+ when 'project'
78
+ Omnitest.filter_projects(project_regexp).each do | project |
79
+ bind_project_variables(project)
80
+ process_directory
81
+ end
82
+ when 'scenario'
83
+ Omnitest.scenarios(project_regexp, scenario_regexp).each do | scenario |
84
+ bind_scenario_variables(scenario)
85
+ process_directory
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def scenario_output_snippet(project_regex, scenario_regex, opts = {})
94
+ scenario = Omnitest.scenarios(project_regex, scenario_regex).first
95
+ fail "Output is not available for #{scenario_name} because that scenario does not exist" unless scenario
96
+ fail "Output is not available for #{scenario_name} because it has not been executed" unless scenario.result
97
+ snippetize_output(scenario.result, opts)
98
+ end
99
+
100
+ def process_directory
101
+ directory Pathname(options[:source]).expand_path, Pathname(options[:destination]).expand_path
102
+ end
103
+
104
+ def bind_project_variables(project)
105
+ @project = project
106
+ @project_name = project.name
107
+ @project_basedir = project.basedir
108
+ end
109
+
110
+ def bind_scenario_variables(scenario)
111
+ bind_project_variables(scenario.project)
112
+ @scenario = scenario
113
+ @scenario_name = scenario.name
114
+ @scenario_slug = scenario.slug
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,62 @@
1
+ require 'omnitest/reporters'
2
+ module Omnitest
3
+ module Command
4
+ class List < Omnitest::Command::Base
5
+ include Omnitest::Reporters
6
+
7
+ def call
8
+ setup
9
+ @reporter = Omnitest::Reporters.reporter(options[:format], shell)
10
+ tests = parse_subcommand(args.shift, args.shift)
11
+
12
+ table = [header_row]
13
+ table += tests.map do | scenario |
14
+ row(scenario)
15
+ end
16
+ print_table(table)
17
+ end
18
+
19
+ private
20
+
21
+ def header_row
22
+ row = []
23
+ row << colorize('Suite', :green)
24
+ row << colorize('Scenario', :green)
25
+ row << colorize('Project', :green)
26
+ row << colorize('Status', :green)
27
+ row << colorize('Source', :green) if options[:source]
28
+ row
29
+ end
30
+
31
+ def row(scenario)
32
+ row = []
33
+ row << color_pad(scenario.suite)
34
+ row << color_pad(scenario.name)
35
+ row << color_pad(scenario.psychic.name)
36
+ row << format_status(scenario)
37
+ if options[:source]
38
+ source_file = scenario.absolute_source_file ? scenario.source_file : colorize('<No code sample>', :red)
39
+ row << source_file
40
+ end
41
+ row
42
+ end
43
+
44
+ def print_table(*args)
45
+ @reporter.print_table(*args)
46
+ end
47
+
48
+ def colorize(string, *args)
49
+ return string unless @reporter.respond_to? :set_color
50
+ @reporter.set_color(string, *args)
51
+ end
52
+
53
+ def color_pad(string)
54
+ string + colorize('', :white)
55
+ end
56
+
57
+ def format_status(scenario)
58
+ colorize(scenario.status_description, scenario.status_color)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,26 @@
1
+ require 'benchmark'
2
+
3
+ module Omnitest
4
+ module Command
5
+ class ProjectAction < Omnitest::Command::Base
6
+ include RunAction
7
+
8
+ # Invoke the command.
9
+ def call
10
+ banner "Starting Omnitest (v#{Omnitest::VERSION})"
11
+ elapsed = Benchmark.measure do
12
+ setup
13
+ project_regex = args.shift
14
+ if %w(task workflow).include? action # a bit hacky...
15
+ argument = project_regex
16
+ project_regex = args.shift
17
+ args.unshift argument
18
+ end
19
+ projects = select_projects(project_regex, options)
20
+ run_action(projects, action, options[:concurrency], *args)
21
+ end
22
+ banner "Omnitest is finished. #{Core::Util.duration(elapsed.real)}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'benchmark'
2
+
3
+ module Omnitest
4
+ module Command
5
+ class ScenarioAction < Omnitest::Command::Base
6
+ include RunAction
7
+
8
+ # Invoke the command.
9
+ def call
10
+ banner "Starting Omnitest (v#{Omnitest::VERSION})"
11
+ elapsed = Benchmark.measure do
12
+ setup
13
+ scenarios = parse_subcommand(args.shift, args.shift)
14
+ run_action(scenarios, action, options[:concurrency])
15
+ end
16
+ banner "Omnitest is finished. #{Core::Util.duration(elapsed.real)}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,148 @@
1
+ require 'omnitest/reporters'
2
+
3
+ module Omnitest
4
+ module Command
5
+ class Show < Omnitest::Command::Base # rubocop:disable Metrics/ClassLength
6
+ include Omnitest::Reporters
7
+ include Omnitest::Core::Util::String
8
+ include Omnitest::Core::FileSystem
9
+
10
+ def call
11
+ @indent_level = 0
12
+ setup
13
+ @reporter = Omnitest::Reporters.reporter(options[:format], shell)
14
+ scenarios = parse_subcommand(args.shift, args.shift)
15
+
16
+ scenarios.each do | scenario |
17
+ status_color = scenario.status_color.to_sym
18
+ status(scenario.slug, colorize(scenario.status_description, status_color), status_color)
19
+ indent do
20
+ status('Test suite', scenario.suite)
21
+ status('Test scenario', scenario.name)
22
+ status('Project', scenario.psychic.name)
23
+ source_file = if scenario.code_sample
24
+ Core::FileSystem.relativize(scenario.absolute_source_file, Dir.pwd)
25
+ else
26
+ colorize('<No code sample>', :red)
27
+ end
28
+ status('Source', source_file)
29
+ display_source(scenario)
30
+ display_execution_result(scenario)
31
+ display_validations(scenario)
32
+ display_spy_data(scenario)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def reformat(string)
40
+ return if string.nil? || string.empty?
41
+
42
+ indent do
43
+ string.gsub(/^/, indent)
44
+ end
45
+ end
46
+
47
+ def indent
48
+ if block_given?
49
+ @indent_level += 2
50
+ result = yield
51
+ @indent_level -= 2
52
+ result
53
+ else
54
+ ' ' * @indent_level
55
+ end
56
+ end
57
+
58
+ def display_source(test)
59
+ return if !options[:source] || !test.source?
60
+
61
+ shell.say test.highlighted_code
62
+ end
63
+
64
+ def display_execution_result(test)
65
+ return if test.result.nil? || test.result.execution_result.nil?
66
+
67
+ execution_result = test.result.execution_result
68
+ status 'Execution result'
69
+ indent do
70
+ status('Exit Status', execution_result.exitstatus)
71
+ status 'Stdout'
72
+ say reformat(execution_result.stdout)
73
+ status 'Stderr'
74
+ say reformat(execution_result.stderr)
75
+ end
76
+ end
77
+
78
+ def display_validations(test)
79
+ return if test.validations.nil?
80
+
81
+ status 'Validations'
82
+ indent do
83
+ test.validations.each do | name, validation |
84
+ status(name, indicator(validation))
85
+ indent do
86
+ status 'Error message', validation.error if validation.error
87
+ unless !options[:source] || !validation.error_source?
88
+ status 'Validator source'
89
+ say highlight(validation.error_source, language: 'ruby')
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def display_spy_data(test)
97
+ return if test.spy_data.nil?
98
+
99
+ status 'Data from spies'
100
+ indent do
101
+ test.spy_data.each do |_spy, data|
102
+ indent do
103
+ data.each_pair do |k, v|
104
+ status(k, v)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ def say(msg)
112
+ shell.say msg if msg
113
+ end
114
+
115
+ def status(status, msg = nil, color = :cyan, colwidth = 50)
116
+ msg = yield if block_given?
117
+ shell.say(indent)
118
+ status = shell.set_color("#{status}:", color, true)
119
+ # The built-in say_status is right-aligned, we want left-aligned
120
+ shell.say format("%-#{colwidth}s %s", status, msg).rstrip
121
+ end
122
+
123
+ def print_table(*args)
124
+ @reporter.print_table(*args)
125
+ end
126
+
127
+ def colorize(string, *args)
128
+ return string unless @reporter.respond_to? :set_color
129
+ @reporter.set_color(string, *args)
130
+ end
131
+
132
+ def color_pad(string)
133
+ string + colorize('', :white)
134
+ end
135
+
136
+ def indicator(validation)
137
+ case validation.status
138
+ when :passed
139
+ colorize("\u2713 Passed", :green)
140
+ when :failed
141
+ colorize('x Failed', :red)
142
+ else
143
+ colorize(validation.status, :yellow)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end