Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add consistent reporting

  • Loading branch information...
commit afdc3ace3aa58873c530bf5005d8817bf4e32e0f 1 parent 8f5579b
Jared Pace jdpace authored
2  koality.gemspec
View
@@ -19,6 +19,8 @@ Gem::Specification.new do |gem|
gem.add_runtime_dependency 'rails_best_practices', ['~> 1.9']
gem.add_runtime_dependency 'simplecov', ['~> 0.6']
gem.add_runtime_dependency 'cane', ['~> 1.3']
+ gem.add_runtime_dependency 'terminal-table', ['~> 1.4']
+ gem.add_runtime_dependency 'term-ansicolor', ['~> 1.0']
# Developmnet Dependencies
gem.add_development_dependency 'rspec', ['~> 2.10']
3  lib/koality.rb
View
@@ -1,5 +1,8 @@
require 'koality/version'
require 'koality/options'
+require 'koality/reporter/base'
+require 'koality/reporter/cane'
+require 'koality/reporter/rails_best_practices'
require 'koality/runner/cane'
require 'koality/runner/rails_best_practices'
8 lib/koality/options.rb
View
@@ -30,7 +30,9 @@ class Options
:custom_thresholds => [],
:total_violations_threshold => 0,
:abort_on_failure => true,
- :output_directory => 'quality'
+ :output_directory => 'quality',
+
+ :colorize_output => true
}
attr_accessor :opts
@@ -74,6 +76,10 @@ def code_coverage_enabled?
code_coverage_enabled
end
+ def colorize_output?
+ colorize_output
+ end
+
def respond_to_missing?(method)
writer?(method) || reader?(method)
end
39 lib/koality/reporter/base.rb
View
@@ -0,0 +1,39 @@
+require 'terminal-table'
+require 'term/ansicolor'
+require 'benchmark'
+
+class String
+ include Term::ANSIColor
+end
+
+module Koality
+ module Reporter
+ class Base
+
+ def self.start(&block)
+ reporter = new
+
+ time = Benchmark.measure do
+ yield reporter
+ end
+
+ puts "-- #{'%0.3f' % time.real}s\n\n"
+ end
+
+ private
+
+ def color(message, color_name)
+ if Koality.options.colorize_output?
+ message.to_s.send(color_name)
+ else
+ message
+ end
+ end
+
+ def build_table
+ Terminal::Table.new :style => {:width => 140, :padding_left => 2, :padding_right => 2}
+ end
+
+ end
+ end
+end
64 lib/koality/reporter/cane.rb
View
@@ -0,0 +1,64 @@
+module Koality
+ module Reporter
+ class Cane < Base
+
+ def report(type, violations)
+ unless violations.count > 0
+ report_success(type)
+ return
+ end
+
+ show_table(type, violations)
+ end
+
+ def show_table(type, errors)
+ return if errors.empty?
+
+ table = build_table
+ table.title = color("Cane - #{type} - #{errors.count} Errors", :bold)
+
+ if type == :style
+ by_message = errors.group_by { |e| e.message.split(/\(\d+\)/).first }
+ by_message.each do |message, errors|
+ msg = color(message, :red)
+ locations = errors.map { |e| " #{e.file_name}:#{e.line}" }
+
+ table.add_row ["#{msg}\n#{locations.join("\n")}", errors.count]
+ table.add_row :separator unless message == by_message.keys.last
+ end
+ else
+ errors.each do |error|
+ table.add_row columns_for_type(type, error)
+ table.add_row :separator unless error == errors.last
+ end
+ end
+
+ puts table
+ end
+
+ def columns_for_type(type, error)
+ case type
+ when :abc
+ ["#{color(error.detail, :red)}\n #{error.file_name}", error.complexity]
+ when :style
+ ["#{color(error.message, :red)}\n #{error.file_name}:#{error.line}"]
+ when :threshold
+ [color(error.name, :red), "expected: #{error.operator} #{color(error.limit, :green)}, actual: #{color(error.value, :red)}"]
+ else
+ error.columns
+ end
+ end
+
+ private
+
+ def report_success(type)
+ puts color("Cane - #{type} - 0 Errors", :green)
+ end
+
+ def grouped_errors(errors)
+ errors.group_by(&:url)
+ end
+
+ end
+ end
+end
44 lib/koality/reporter/rails_best_practices.rb
View
@@ -0,0 +1,44 @@
+module Koality
+ module Reporter
+ class RailsBestPractices < Base
+
+ attr_reader :table
+
+ def initialize
+ @table = build_table
+ end
+
+ def report(errors)
+ unless errors.count > 0
+ report_success
+ return
+ end
+
+ table.title = color("Rails Best Practices - #{errors.count} Errors", :bold)
+ rows = grouped_errors(errors).map do |message, errors|
+ info = "#{color(message, :red)}\n#{color(errors.first.url, :cyan)}\n"
+ info << errors.map { |e| " #{e.short_filename}:#{e.line_number}" }.join("\n")
+ [info, errors.count]
+ end
+
+ rows.each do |row|
+ table.add_row row
+ table.add_row :separator unless row == rows.last
+ end
+
+ puts table
+ end
+
+ private
+
+ def report_success
+ puts color("Rails Best Practices - 0 Errors", :green)
+ end
+
+ def grouped_errors(errors)
+ errors.group_by(&:message)
+ end
+
+ end
+ end
+end
39 lib/koality/runner/cane.rb
View
@@ -12,30 +12,33 @@ def initialize(options)
end
def run
- ::Cane.run(cane_options)
+ violations.clear
+ checkers.each { |type, _| run_checker(type) }
+ success?
end
- #def run
- #violations.clear
+ def checkers
+ ::Cane::Runner::CHECKERS.select { |type, _| cane_options.key?(type) }
+ end
- #checkers.each do |type, klass|
- #checker = klass.new(cane_options[type])
- #violations[type] = checker.violations
- #end
+ def run_checker(type)
+ Koality::Reporter::Cane.start do |reporter|
+ checker = checkers[type].new(cane_options[type])
+ self.violations[type] = checker.violations
- #violations_count <= cane_options[:max_violations]
- #end
+ reporter.report(type, violations[type])
+ end
+ end
- #def checkers
- #::Cane::Runner::CHECKERS.select { |type, _| cane_options.key?(type) }
- #end
+ def success?
+ # TODO: Allow granular thresholds
+ # e.g. code coverage failure returns false but
+ # you could have 10 style errors before returning false
+ violations.values.flatten.empty?
+ end
private
- def violations_count
- violations.values.flatten.count
- end
-
def translate_options(options)
Hash.new.tap do |cane_opts|
cane_opts[:max_violations] = options[:total_violations_threshold]
@@ -53,10 +56,12 @@ def translate_options(options)
:files => options[:doc_file_pattern]
} if options[:doc_enabled]
- cane_opts[:threshold] = options.thresholds
+ cane_opts[:threshold] = options.thresholds if options.thresholds.any?
end
end
+
+
end
end
end
14 lib/koality/runner/rails_best_practices.rb
View
@@ -13,9 +13,13 @@ def initialize(options)
def run
analyzer = ::RailsBestPractices::Analyzer.new('.', rbp_options)
- analyzer.analyze
- File.open(@output_file,'w') do |f|
+ Koality::Reporter::RailsBestPractices.start do |reporter|
+ analyzer.analyze
+ reporter.report(analyzer.errors)
+ end
+
+ File.open(output_file, 'w') do |f|
f << analyzer.errors.count
end
end
@@ -24,8 +28,9 @@ def run
def translate_options(options)
{
- :only => regexp_list(options[:rails_bp_accept_patterns]),
- :except => regexp_list(options[:rails_bp_ignore_patterns])
+ 'silent' => true,
+ 'only' => regexp_list(options[:rails_bp_accept_patterns]),
+ 'except' => regexp_list(options[:rails_bp_ignore_patterns])
}
end
@@ -34,5 +39,6 @@ def regexp_list(list)
end
end
+
end
end
83 spec/koality/runner/cane_spec.rb
View
@@ -17,18 +17,18 @@
:doc_file_pattern => '{app,lib}/**/.rb',
:doc_enabled => false,
- :thresholds => [],
+ :custom_thresholds => [[:>=, 'quality/foo', 42]],
:total_violations_threshold => 5
})
end
+ let(:runner) { runner = Koality::Runner::Cane.new options }
describe '.new' do
it 'translates Koality options into the format Cane expects' do
- runner = Koality::Runner::Cane.new options
copts = runner.cane_options
copts[:max_violations].should == 5
- copts[:threshold].should == []
+ copts[:threshold].should == [[:>=, 'quality/foo', 42]]
copts[:abc].should == {
:files => '{app,lib}/**/.rb',
@@ -44,13 +44,84 @@
end
end
+ describe '#checkers' do
+ it 'returns all checkers which have configured options' do
+ runner.checkers.values.should include(
+ Cane::Runner::CHECKERS[:abc],
+ Cane::Runner::CHECKERS[:style],
+ Cane::Runner::CHECKERS[:threshold]
+ )
+ runner.checkers.values.should_not include(Cane::Runner::CHECKERS[:doc])
+ end
+ end
+
+ describe '#run_checker' do
+ let(:checker) { stub('checker', :violations => [:violation_1, :violation_2]) }
+ let(:reporter) { stub('reporter', :report => true) }
+
+ before do
+ Koality::Reporter::Cane.stubs(:start).yields(reporter)
+ runner.checkers[:abc].stubs(:new).returns(checker)
+ end
+
+ it 'builds and runs the specified checker' do
+ runner.checkers[:abc].expects(:new).with(runner.cane_options[:abc]).returns(checker)
+ checker.expects(:violations)
+
+ runner.run_checker(:abc)
+ end
+
+ it 'saves the violations' do
+ runner.run_checker(:abc)
+ runner.violations[:abc].should == [:violation_1, :violation_2]
+ end
+
+ it 'reports the violations' do
+ reporter.expects(:report).with(:abc, [:violation_1, :violation_2])
+ runner.run_checker(:abc)
+ end
+ end
+
describe '#run' do
- it 'runs the Cane gem with the translated options' do
- runner = Koality::Runner::Cane.new options
- ::Cane.expects(:run).with(runner.cane_options)
+ before do
+ runner.stubs(:run_checker)
+ end
+ it 'clears the violations' do
+ runner.violations.expects(:clear)
runner.run
end
+
+ it 'runs each checker' do
+ runner.stubs(:checkers).returns({
+ :abc => (abc = stub('abc_checker')),
+ :style => (style = stub('style_checker'))
+ })
+
+ runner.expects(:run_checker).with(:abc)
+ runner.expects(:run_checker).with(:style)
+ runner.run
+ end
+
+ it 'returns whether or not the run was successful' do
+ runner.expects(:success?).returns(true)
+ runner.run.should eql(true)
+
+ runner.expects(:success?).returns(false)
+ runner.run.should eql(false)
+ end
+ end
+
+ describe '#success?' do
+ it 'returns true if there are no violations' do
+ runner.stubs(:violations).returns({:abc => [], :style => []})
+ runner.success?.should be_true
+ end
+
+ it 'returns false if there are any violations' do
+ runner.stubs(:violations).returns({:abc => [], :style => [:violation_1]})
+ runner.success?.should be_false
+ end
end
end
29 spec/koality/runner/rails_best_practices_spec.rb
View
@@ -11,45 +11,54 @@
:output_directory => 'quality'
})
end
+ let(:runner) { runner = Koality::Runner::RailsBestPractices.new options }
describe '.new' do
it 'figures out the output file path' do
- runner = Koality::Runner::RailsBestPractices.new options
runner.output_file.should == options.output_file(:rails_bp_error_file)
end
it 'translates the options into what RBP expects' do
- runner = Koality::Runner::RailsBestPractices.new options
rbp_opts = runner.rbp_options
- rbp_opts[:only].should == [Regexp.new("app/controllers/.+\\.rb")]
- rbp_opts[:except].should == [/app\/helpers\/foo_helper\.rb/]
+ rbp_opts['only'].should == [Regexp.new("app/controllers/.+\\.rb")]
+ rbp_opts['except'].should == [/app\/helpers\/foo_helper\.rb/]
end
end
describe '#run' do
+ let(:reporter) { stub('reporter', :report => true) }
+ let(:rbp) { rbp = stub('RailsBestPractices', :analyze => true, :errors => []) }
+
before do
FileUtils.mkdir_p options.output_directory
+ Koality::Reporter::RailsBestPractices.stubs(:start).yields(reporter)
+ RailsBestPractices::Analyzer.stubs(:new).returns(rbp)
end
it 'runs a RBP analyzer with the translated options' do
- runner = Koality::Runner::RailsBestPractices.new options
- rbp = mock('RailsBestPractices', :analyze => true, :errors => [])
- ::RailsBestPractices::Analyzer.expects(:new).with('.', runner.rbp_options).returns(rbp)
+ RailsBestPractices::Analyzer.expects(:new).with('.', runner.rbp_options).returns(rbp)
+ rbp.expects(:analyze)
runner.run
end
it 'creates a file with the number of failures from the run' do
- runner = Koality::Runner::RailsBestPractices.new options
errors = [stub('error')] * rand(5)
- rbp = stub('RailsBestPractices', :analyze => true, :errors => errors)
- ::RailsBestPractices::Analyzer.stubs(:new).returns(rbp)
+ rbp.stubs(:errors).returns(errors)
runner.run
Pathname.new(runner.output_file).read.should == errors.count.to_s
end
+
+ it 'reports the errors' do
+ errors = [stub('error')] * rand(5)
+ rbp.stubs(:errors).returns(errors)
+
+ reporter.expects(:report).with(errors)
+ runner.run
+ end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.