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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +128 -0
- data/Rakefile +6 -0
- data/koality.gemspec +28 -0
- data/lib/koality.rb +43 -0
- data/lib/koality/options.rb +120 -0
- data/lib/koality/rake_task.rb +25 -0
- data/lib/koality/reporter/base.rb +39 -0
- data/lib/koality/reporter/cane.rb +64 -0
- data/lib/koality/reporter/rails_best_practices.rb +44 -0
- data/lib/koality/runner/cane.rb +78 -0
- data/lib/koality/runner/rails_best_practices.rb +44 -0
- data/lib/koality/simplecov.rb +2 -0
- data/lib/koality/simplecov/formatter.rb +21 -0
- data/lib/koality/version.rb +3 -0
- data/spec/koality/options_spec.rb +156 -0
- data/spec/koality/rake_task_spec.rb +25 -0
- data/spec/koality/runner/cane_spec.rb +139 -0
- data/spec/koality/runner/rails_best_practices_spec.rb +64 -0
- data/spec/koality/simplecov/formatter_spec.rb +36 -0
- data/spec/koality_spec.rb +85 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/with_constants.rb +43 -0
- metadata +190 -0
@@ -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,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,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
|