koality 1.0.0

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.
@@ -0,0 +1,64 @@
1
+ module Koality
2
+ module Reporter
3
+ class Cane < Base
4
+
5
+ def report(type, violations)
6
+ unless violations.count > 0
7
+ report_success(type)
8
+ return
9
+ end
10
+
11
+ show_table(type, violations)
12
+ end
13
+
14
+ def show_table(type, errors)
15
+ return if errors.empty?
16
+
17
+ table = build_table
18
+ table.title = color("Cane - #{type} - #{errors.count} Errors", :bold)
19
+
20
+ if type == :style
21
+ by_message = errors.group_by { |e| e.message.split(/\(\d+\)/).first }
22
+ by_message.each do |message, errors|
23
+ msg = color(message, :red)
24
+ locations = errors.map { |e| " #{e.file_name}:#{e.line}" }
25
+
26
+ table.add_row ["#{msg}\n#{locations.join("\n")}", errors.count]
27
+ table.add_row :separator unless message == by_message.keys.last
28
+ end
29
+ else
30
+ errors.each do |error|
31
+ table.add_row columns_for_type(type, error)
32
+ table.add_row :separator unless error == errors.last
33
+ end
34
+ end
35
+
36
+ puts table
37
+ end
38
+
39
+ def columns_for_type(type, error)
40
+ case type
41
+ when :abc
42
+ ["#{color(error.detail, :red)}\n #{error.file_name}", error.complexity]
43
+ when :style
44
+ ["#{color(error.message, :red)}\n #{error.file_name}:#{error.line}"]
45
+ when :threshold
46
+ [color(error.name, :red), "expected: #{error.operator} #{color(error.limit, :green)}, actual: #{color(error.value, :red)}"]
47
+ else
48
+ error.columns
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def report_success(type)
55
+ puts color("Cane - #{type} - 0 Errors", :green)
56
+ end
57
+
58
+ def grouped_errors(errors)
59
+ errors.group_by(&:url)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ module Koality
2
+ module Reporter
3
+ class RailsBestPractices < Base
4
+
5
+ attr_reader :table
6
+
7
+ def initialize
8
+ @table = build_table
9
+ end
10
+
11
+ def report(errors)
12
+ unless errors.count > 0
13
+ report_success
14
+ return
15
+ end
16
+
17
+ table.title = color("Rails Best Practices - #{errors.count} Errors", :bold)
18
+ rows = grouped_errors(errors).map do |message, errors|
19
+ info = "#{color(message, :red)}\n#{color(errors.first.url, :cyan)}\n"
20
+ info << errors.map { |e| " #{e.short_filename}:#{e.line_number}" }.join("\n")
21
+ [info, errors.count]
22
+ end
23
+
24
+ rows.each do |row|
25
+ table.add_row row
26
+ table.add_row :separator unless row == rows.last
27
+ end
28
+
29
+ puts table
30
+ end
31
+
32
+ private
33
+
34
+ def report_success
35
+ puts color("Rails Best Practices - 0 Errors", :green)
36
+ end
37
+
38
+ def grouped_errors(errors)
39
+ errors.group_by(&:message)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,78 @@
1
+ require 'cane'
2
+
3
+ module Koality
4
+ module Runner
5
+ class Cane
6
+
7
+ attr_reader :cane_options, :violations
8
+
9
+ def initialize(options)
10
+ @cane_options = translate_options(options)
11
+ @violations = {}
12
+ end
13
+
14
+ def run
15
+ violations.clear
16
+ checkers.each { |type, _| run_checker(type) }
17
+ success?
18
+ end
19
+
20
+ def checkers
21
+ ::Cane::Runner::CHECKERS.select { |type, _| cane_options.key?(type) }
22
+ end
23
+
24
+ def run_checker(type)
25
+ Koality::Reporter::Cane.start do |reporter|
26
+ checker = checkers[type].new(cane_options[type])
27
+ self.violations[type] = checker.violations
28
+
29
+ reporter.report(type, violations[type])
30
+ end
31
+ end
32
+
33
+ def success?
34
+ return false if exceeds_total_violations_threshold?
35
+ violations.none? { |type, _| exceeds_violations_threshold?(type) }
36
+ end
37
+
38
+ def exceeds_total_violations_threshold?
39
+ max_violations = Koality.options.total_violations_threshold
40
+ total_violations = violations.values.flatten.count
41
+
42
+ max_violations >= 0 && total_violations > max_violations
43
+ end
44
+
45
+ def exceeds_violations_threshold?(type)
46
+ max_violations = Koality.options[:"#{type}_violations_threshold"].to_i
47
+ total_violations = violations[type].count
48
+
49
+ max_violations >= 0 && total_violations > max_violations
50
+ end
51
+
52
+ private
53
+
54
+ def translate_options(options)
55
+ Hash.new.tap do |cane_opts|
56
+ cane_opts[:abc] = {
57
+ :files => options[:abc_file_pattern],
58
+ :max => options[:abc_threshold]
59
+ } if options[:abc_enabled]
60
+
61
+ cane_opts[:style] = {
62
+ :files => options[:style_file_pattern],
63
+ :measure => options[:style_line_length_threshold]
64
+ } if options[:style_enabled]
65
+
66
+ cane_opts[:doc] = {
67
+ :files => options[:doc_file_pattern]
68
+ } if options[:doc_enabled]
69
+
70
+ cane_opts[:threshold] = options.thresholds if options.thresholds.any?
71
+ end
72
+ end
73
+
74
+
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,44 @@
1
+ module Koality
2
+ module Runner
3
+ class RailsBestPractices
4
+
5
+ attr_reader :output_file, :rbp_options
6
+
7
+ def initialize(options)
8
+ require 'rails_best_practices'
9
+
10
+ @output_file = options.output_file(:rails_bp_error_file)
11
+ @rbp_options = translate_options(options)
12
+ end
13
+
14
+ def run
15
+ analyzer = ::RailsBestPractices::Analyzer.new('.', rbp_options)
16
+
17
+ Koality::Reporter::RailsBestPractices.start do |reporter|
18
+ analyzer.analyze
19
+ reporter.report(analyzer.errors)
20
+ end
21
+
22
+ File.open(output_file, 'w') do |f|
23
+ f << analyzer.errors.count
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def translate_options(options)
30
+ {
31
+ 'silent' => true,
32
+ 'only' => regexp_list(options[:rails_bp_accept_patterns]),
33
+ 'except' => regexp_list(options[:rails_bp_ignore_patterns])
34
+ }
35
+ end
36
+
37
+ def regexp_list(list)
38
+ list.map { |item| Regexp.new item }
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,2 @@
1
+ require 'koality/simplecov/formatter'
2
+ SimpleCov.formatter = Koality::SimpleCov::Formatter
@@ -0,0 +1,21 @@
1
+ require 'koality'
2
+ require 'simplecov'
3
+
4
+ module Koality
5
+ module SimpleCov
6
+ class Formatter
7
+
8
+ def format(result)
9
+ ::SimpleCov::Formatter::HTMLFormatter.new.format(result)
10
+
11
+ Koality.options.ensure_output_directory_exists
12
+ threshold_file = Koality.options.output_file(:code_coverage_file)
13
+
14
+ File.open(threshold_file, "w") do |f|
15
+ f << result.source_files.covered_percent.to_f
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Koality
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe Koality::Options do
4
+
5
+ let(:default_options) do
6
+ {
7
+ :abc_enabled => true,
8
+ :style_enabled => true,
9
+
10
+ :code_coverage_threshold => 90,
11
+ :code_coverage_file => 'code_coverage',
12
+ :code_coverage_enabled => true,
13
+
14
+ :rails_bp_error_file => 'rails_bp_errors',
15
+ :rails_bp_errors_threshold => 5,
16
+ :rails_bp_enabled => true,
17
+
18
+ :custom_thresholds => [],
19
+ :total_violations_threshold => 0,
20
+ :abort_on_failure => true,
21
+ :output_directory => 'quality'
22
+ }
23
+ end
24
+
25
+ describe '.new' do
26
+ it 'allows you to override default options' do
27
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
28
+ opts = Koality::Options.new(:abc_enabled => false, :style_enabled => false)
29
+
30
+ opts[:abc_enabled].should be_false
31
+ opts[:style_enabled].should be_false
32
+ opts[:code_coverage_enabled].should be_true
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#[]' do
38
+ it 'delegates to the underlying options' do
39
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
40
+ opts = Koality::Options.new
41
+ opts[:abc_enabled].should be_true
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#[]=' do
47
+ it 'delegates to the underlying options' do
48
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
49
+ opts = Koality::Options.new
50
+ opts[:abc_enabled] = false
51
+ opts[:abc_enabled].should be_false
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'dynamic reader methods' do
57
+ it 'delegates to the underlying options' do
58
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
59
+ opts = Koality::Options.new
60
+ opts.abc_enabled.should be_true
61
+ end
62
+ end
63
+ end
64
+
65
+ describe 'dynamic reader methods' do
66
+ it 'delegates to the underlying options' do
67
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
68
+ opts = Koality::Options.new
69
+ opts.abc_enabled = false
70
+ opts.abc_enabled.should be_false
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#add_threshold' do
76
+ it 'adds a new threshold in the format Cane expects' do
77
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
78
+ opts = Koality::Options.new
79
+ opts.add_threshold('quality/foo', :>=, 99)
80
+ opts.custom_thresholds.should == [[:>=, 'quality/foo', 99]]
81
+
82
+ opts.add_threshold('quality/bar', :==, 75)
83
+ opts.custom_thresholds.should == [[:>=, 'quality/foo', 99], [:==, 'quality/bar', 75]]
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#runner_thresholds' do
89
+ it 'is empty if no extra runners are enabled' do
90
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
91
+ opts = Koality::Options.new(:rails_bp_enabled => false, :code_coverage_enabled => false)
92
+ opts.runner_thresholds.should be_empty
93
+ end
94
+ end
95
+
96
+ it 'includes a threshold check for Rails BP if enabled' do
97
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
98
+ opts = Koality::Options.new
99
+ opts.stubs(:rails_bp_enabled? => true)
100
+ opts.runner_thresholds.should include([:<=, 'quality/rails_bp_errors', 5])
101
+ end
102
+ end
103
+
104
+ it 'includes a threshold check for code coverage if enabled' do
105
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
106
+ opts = Koality::Options.new
107
+ opts.stubs(:code_coverage_enabled? => true)
108
+ opts.runner_thresholds.should include([:>=, 'quality/code_coverage', 90])
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#thresholds' do
114
+ it 'returns the union of custom_thresholds and runner_thresholds' do
115
+ opts = Koality::Options.new
116
+ opts.stubs(:custom_thresholds => [:c1, :c2], :runner_thresholds => [:r1])
117
+ opts.thresholds.should == [:c1, :c2, :r1]
118
+ end
119
+ end
120
+
121
+ describe '#output_file' do
122
+ it 'returns the path for a file relative to the output directory' do
123
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
124
+ opts = Koality::Options.new
125
+ opts.output_file(:rails_bp_error_file).should == 'quality/rails_bp_errors'
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#rails_bp_enabled?' do
131
+ context 'when inside a rails project' do
132
+ it 'returns true if the :rails_bp_enabled flag is set to true' do
133
+ Koality::Options.with_constants(:Rails => true, :DEFAULTS => default_options) do
134
+ opts = Koality::Options.new(:rails_bp_enabled => true)
135
+ opts.rails_bp_enabled?.should be_true
136
+ end
137
+ end
138
+
139
+ it 'returns false if the :rails_bp_enabled flag is set to false' do
140
+ Koality::Options.with_constants(:Rails => true, :DEFAULTS => default_options) do
141
+ opts = Koality::Options.new(:rails_bp_enabled => false)
142
+ opts.rails_bp_enabled?.should be_false
143
+ end
144
+ end
145
+ end
146
+
147
+ context 'when not inside a rails project' do
148
+ it 'returns false' do
149
+ Koality::Options.with_constants(:DEFAULTS => default_options) do
150
+ opts = Koality::Options.new(:rails_bp_enabled => true)
151
+ opts.rails_bp_enabled?.should be_false
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'koality/rake_task'
3
+
4
+ describe Koality::RakeTask do
5
+
6
+ before do
7
+ Rake::Task.clear
8
+ end
9
+
10
+ describe '.new' do
11
+ it 'allows you to configure options' do
12
+ task = Koality::RakeTask.new do |options|
13
+ options.should == Koality.options
14
+ end
15
+ end
16
+
17
+ it 'defines a task that runs Koality with the options' do
18
+ Koality::RakeTask.new
19
+ task = Rake::Task[:koality]
20
+
21
+ Koality.expects(:run)
22
+ task.invoke
23
+ end
24
+ end
25
+ end