equalizer 0.0.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.
- data/.gitignore +37 -0
- data/.rvmrc +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +56 -0
- data/Guardfile +18 -0
- data/LICENSE +21 -0
- data/README.md +105 -0
- data/Rakefile +9 -0
- data/TODO +0 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +91 -0
- data/config/yardstick.yml +2 -0
- data/equalizer.gemspec +24 -0
- data/lib/equalizer.rb +121 -0
- data/lib/equalizer/version.rb +5 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/command_method_behavior.rb +7 -0
- data/spec/shared/each_method_behaviour.rb +15 -0
- data/spec/shared/hash_method_behavior.rb +17 -0
- data/spec/shared/idempotent_method_behavior.rb +7 -0
- data/spec/shared/invertible_method_behaviour.rb +9 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/config_alias.rb +3 -0
- data/spec/unit/equalizer/class_method/new_spec.rb +134 -0
- data/spec/unit/equalizer/methods/eql_spec.rb +49 -0
- data/spec/unit/equalizer/methods/equal_value_spec.rb +85 -0
- data/tasks/metrics/ci.rake +9 -0
- data/tasks/metrics/flay.rake +45 -0
- data/tasks/metrics/flog.rake +49 -0
- data/tasks/metrics/heckle.rake +208 -0
- data/tasks/metrics/metric_fu.rake +31 -0
- data/tasks/metrics/reek.rake +21 -0
- data/tasks/metrics/roodi.rake +19 -0
- data/tasks/metrics/yardstick.rake +25 -0
- data/tasks/spec.rake +60 -0
- data/tasks/yard.rake +11 -0
- metadata +172 -0
data/spec/rcov.opts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'an #each method' do
|
4
|
+
it_should_behave_like 'a command method'
|
5
|
+
|
6
|
+
context 'with no block' do
|
7
|
+
subject { object.each }
|
8
|
+
|
9
|
+
it { should be_instance_of(to_enum.class) }
|
10
|
+
|
11
|
+
it 'yields the expected values' do
|
12
|
+
subject.to_a.should eql(object.to_a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'a hash method' do
|
4
|
+
it_should_behave_like 'an idempotent method'
|
5
|
+
|
6
|
+
specification = proc do
|
7
|
+
should be_instance_of(Fixnum)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'is a fixnum' do
|
11
|
+
instance_eval(&specification)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'memoizes the hash code' do
|
15
|
+
subject.should eql(object.memoized(:hash))
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'equalizer'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
|
7
|
+
# require spec support files and shared behavior
|
8
|
+
Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f }
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Equalizer, '.new' do
|
6
|
+
let(:object) { described_class }
|
7
|
+
let(:name) { 'User' }
|
8
|
+
let(:klass) { ::Class.new }
|
9
|
+
|
10
|
+
context 'with no keys' do
|
11
|
+
subject { object.new }
|
12
|
+
|
13
|
+
before do
|
14
|
+
# specify the class #name method
|
15
|
+
klass.stub(:name).and_return(name)
|
16
|
+
klass.send(:include, subject)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:instance) { klass.new }
|
20
|
+
|
21
|
+
it { should be_instance_of(object) }
|
22
|
+
|
23
|
+
it { should be_frozen }
|
24
|
+
|
25
|
+
it 'defines #hash and #inspect methods dynamically' do
|
26
|
+
subject.public_instance_methods(false).map(&:to_s).should =~ %w[ hash inspect ]
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#eql?' do
|
30
|
+
context 'when the objects are similar' do
|
31
|
+
let(:other) { instance.dup }
|
32
|
+
|
33
|
+
it { instance.eql?(other).should be(true) }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when the objects are different' do
|
37
|
+
let(:other) { stub('other') }
|
38
|
+
|
39
|
+
it { instance.eql?(other).should be(false) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#==' do
|
44
|
+
context 'when the objects are similar' do
|
45
|
+
let(:other) { instance.dup }
|
46
|
+
|
47
|
+
it { (instance == other).should be(true) }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when the objects are different' do
|
51
|
+
let(:other) { stub('other') }
|
52
|
+
|
53
|
+
it { (instance == other).should be(false) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#hash' do
|
58
|
+
it { instance.hash.should eql(klass.hash) }
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#inspect' do
|
62
|
+
it { instance.inspect.should eql('#<User>') }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with keys' do
|
67
|
+
subject { object.new(*keys) }
|
68
|
+
|
69
|
+
let(:keys) { [ :first_name ].freeze }
|
70
|
+
let(:first_name) { 'John' }
|
71
|
+
let(:instance) { klass.new(first_name) }
|
72
|
+
|
73
|
+
let(:klass) do
|
74
|
+
::Class.new do
|
75
|
+
attr_reader :first_name
|
76
|
+
|
77
|
+
def initialize(first_name)
|
78
|
+
@first_name = first_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
before do
|
84
|
+
# specify the class #inspect method
|
85
|
+
klass.stub(:name).and_return(nil)
|
86
|
+
klass.stub(:inspect).and_return(name)
|
87
|
+
klass.send(:include, subject)
|
88
|
+
end
|
89
|
+
|
90
|
+
it { should be_instance_of(object) }
|
91
|
+
|
92
|
+
it { should be_frozen }
|
93
|
+
|
94
|
+
it 'defines #hash and #inspect methods dynamically' do
|
95
|
+
subject.public_instance_methods(false).map(&:to_s).should =~ %w[ hash inspect ]
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#eql?' do
|
99
|
+
context 'when the objects are similar' do
|
100
|
+
let(:other) { instance.dup }
|
101
|
+
|
102
|
+
it { instance.eql?(other).should be(true) }
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when the objects are different' do
|
106
|
+
let(:other) { stub('other') }
|
107
|
+
|
108
|
+
it { instance.eql?(other).should be(false) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#==' do
|
113
|
+
context 'when the objects are similar' do
|
114
|
+
let(:other) { instance.dup }
|
115
|
+
|
116
|
+
it { (instance == other).should be(true) }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when the objects are different' do
|
120
|
+
let(:other) { stub('other') }
|
121
|
+
|
122
|
+
it { (instance == other).should be(false) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#hash' do
|
127
|
+
it { instance.hash.should eql(klass.hash ^ first_name.hash) }
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#inspect' do
|
131
|
+
it { instance.inspect.should eql('#<User first_name="John">') }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Equalizer::Methods, '#eql?' do
|
6
|
+
subject { object.eql?(other) }
|
7
|
+
|
8
|
+
let(:object) { described_class.new }
|
9
|
+
|
10
|
+
let(:described_class) do
|
11
|
+
Class.new do
|
12
|
+
include Equalizer::Methods
|
13
|
+
|
14
|
+
def cmp?(comparator, other)
|
15
|
+
!!(comparator and other)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with the same object' do
|
21
|
+
let(:other) { object }
|
22
|
+
|
23
|
+
it { should be(true) }
|
24
|
+
|
25
|
+
it 'is symmetric' do
|
26
|
+
should eql(other.eql?(object))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with an equivalent object' do
|
31
|
+
let(:other) { object.dup }
|
32
|
+
|
33
|
+
it { should be(true) }
|
34
|
+
|
35
|
+
it 'is symmetric' do
|
36
|
+
should eql(other.eql?(object))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with an equivalent object of a subclass' do
|
41
|
+
let(:other) { Class.new(described_class).new }
|
42
|
+
|
43
|
+
it { should be(false) }
|
44
|
+
|
45
|
+
it 'is symmetric' do
|
46
|
+
should eql(other.eql?(object))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Equalizer::Methods, '#==' do
|
6
|
+
subject { object == other }
|
7
|
+
|
8
|
+
let(:object) { described_class.new(true) }
|
9
|
+
|
10
|
+
let(:described_class) do
|
11
|
+
Class.new do
|
12
|
+
include Equalizer::Methods
|
13
|
+
|
14
|
+
attr_reader :boolean
|
15
|
+
|
16
|
+
def initialize(boolean)
|
17
|
+
@boolean = boolean
|
18
|
+
end
|
19
|
+
|
20
|
+
def cmp?(comparator, other)
|
21
|
+
boolean.send(comparator, other.boolean)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with the same object' do
|
27
|
+
let(:other) { object }
|
28
|
+
|
29
|
+
it { should be(true) }
|
30
|
+
|
31
|
+
it 'is symmetric' do
|
32
|
+
should eql(other == object)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with an equivalent object' do
|
37
|
+
let(:other) { object.dup }
|
38
|
+
|
39
|
+
it { should be(true) }
|
40
|
+
|
41
|
+
it 'is symmetric' do
|
42
|
+
should eql(other == object)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with an equivalent object of a subclass' do
|
47
|
+
let(:other) { Class.new(described_class).new(true) }
|
48
|
+
|
49
|
+
it { should be(true) }
|
50
|
+
|
51
|
+
it 'is symmetric' do
|
52
|
+
should eql(other == object)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with an object of another class' do
|
57
|
+
let(:other) { Class.new.new }
|
58
|
+
|
59
|
+
it { should be(false) }
|
60
|
+
|
61
|
+
it 'is symmetric' do
|
62
|
+
should eql(other == object)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with an equivalent object after coercion' do
|
67
|
+
let(:other) { Object.new }
|
68
|
+
|
69
|
+
before do
|
70
|
+
# declare a private #coerce method
|
71
|
+
described_class.class_eval do
|
72
|
+
def coerce(other)
|
73
|
+
self.class.new(!!other)
|
74
|
+
end
|
75
|
+
private :coerce
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it { should be(true) }
|
80
|
+
|
81
|
+
it 'is not symmetric' do
|
82
|
+
should_not eql(other == object)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
desc 'Run metrics with Heckle'
|
4
|
+
task :ci => %w[ ci:metrics metrics:heckle ]
|
5
|
+
|
6
|
+
namespace :ci do
|
7
|
+
desc 'Run metrics (except heckle) and spec'
|
8
|
+
task :metrics => %w[ spec metrics:verify_measurements metrics:flog metrics:flay metrics:reek metrics:roodi metrics:all ]
|
9
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'flay'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze
|
8
|
+
threshold = config.fetch('threshold').to_i
|
9
|
+
total_score = config.fetch('total_score').to_f
|
10
|
+
files = Flay.expand_dirs_to_files(config.fetch('path', 'lib')).sort
|
11
|
+
|
12
|
+
namespace :metrics do
|
13
|
+
# original code by Marty Andrews:
|
14
|
+
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
|
15
|
+
desc 'Analyze for code duplication'
|
16
|
+
task :flay do
|
17
|
+
# run flay once without a threshold to ensure the max mass matches the threshold
|
18
|
+
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
|
19
|
+
flay.process(*files)
|
20
|
+
|
21
|
+
max = (flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max) || 0
|
22
|
+
unless max >= threshold
|
23
|
+
raise "Adjust flay threshold down to #{max}"
|
24
|
+
end
|
25
|
+
|
26
|
+
total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
|
27
|
+
unless total == total_score
|
28
|
+
raise "Flay total is now #{total}, but expected #{total_score}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# run flay a second time with the threshold set
|
32
|
+
flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
|
33
|
+
flay.process(*files)
|
34
|
+
|
35
|
+
if flay.masses.any?
|
36
|
+
flay.report
|
37
|
+
raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
task :flay do
|
43
|
+
$stderr.puts 'Flay is not available. In order to run flay, you must: gem install flay'
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'flog'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
class Float
|
8
|
+
def round_to(n)
|
9
|
+
(self * 10**n).round.to_f * 10**-n
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
config = YAML.load_file(File.expand_path('../../../config/flog.yml', __FILE__)).freeze
|
14
|
+
threshold = config.fetch('threshold').to_f.round_to(1)
|
15
|
+
|
16
|
+
namespace :metrics do
|
17
|
+
# original code by Marty Andrews:
|
18
|
+
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
|
19
|
+
desc 'Analyze for code complexity'
|
20
|
+
task :flog do
|
21
|
+
flog = Flog.new
|
22
|
+
flog.flog Array(config.fetch('path', 'lib'))
|
23
|
+
|
24
|
+
totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }.
|
25
|
+
map { |name, score| [ name, score.round_to(1) ] }.
|
26
|
+
sort_by { |name, score| score }
|
27
|
+
|
28
|
+
if totals.any?
|
29
|
+
max = totals.last[1]
|
30
|
+
unless max >= threshold
|
31
|
+
raise "Adjust flog score down to #{max}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
bad_methods = totals.select { |name, score| score > threshold }
|
36
|
+
if bad_methods.any?
|
37
|
+
bad_methods.reverse_each do |name, score|
|
38
|
+
puts '%8.1f: %s' % [ score, name ]
|
39
|
+
end
|
40
|
+
|
41
|
+
raise "#{bad_methods.size} methods have a flog complexity > #{threshold}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue LoadError
|
46
|
+
task :flog do
|
47
|
+
$stderr.puts 'Flog is not available. In order to run flog, you must: gem install flog'
|
48
|
+
end
|
49
|
+
end
|