abstract_type 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module AbstractType
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
7
+ --failure-threshold 100
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an invertible method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is invertible' do
7
+ subject.inverse.should equal(object)
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ --color
2
+ --loadby random
3
+ --format profile
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'abstract_type'
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,3 @@
1
+ require 'rbconfig'
2
+
3
+ ::Config = RbConfig unless defined?(::Config)
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe AbstractType::ClassMethods, '#abstract_method' do
6
+ subject { object.some_method }
7
+
8
+ let(:abstract_type) do
9
+ Class.new do
10
+ include AbstractType
11
+
12
+ abstract_method :some_method
13
+ end
14
+ end
15
+
16
+ let(:class_under_test)do
17
+ Class.new(abstract_type) do
18
+ def self.name; 'TheClassName'; end
19
+ end
20
+ end
21
+
22
+ let(:object) { class_under_test.new }
23
+
24
+ it 'creates an abstract method' do
25
+ expect { subject }.to raise_error(NotImplementedError,'TheClassName#some_method is not implemented')
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe AbstractType::ClassMethods, '#abstract_method' do
6
+ subject { object.some_method }
7
+
8
+ let(:object) do
9
+ Class.new do
10
+ include AbstractType
11
+
12
+ abstract_singleton_method :some_method
13
+
14
+ def self.name; 'TheClassName'; end
15
+ end
16
+ end
17
+
18
+ it 'creates an abstract method' do
19
+ expect { subject }.to raise_error(NotImplementedError, 'TheClassName.some_method is not implemented')
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe AbstractType::ClassMethods, '#new' do
6
+ subject { object.new }
7
+
8
+ let(:abstract_type) { Class.new { include AbstractType } }
9
+
10
+ context 'called on a subclass' do
11
+ let(:object) { Class.new(abstract_type) }
12
+
13
+ it { should be_instance_of(object) }
14
+ end
15
+
16
+ context 'called on the class' do
17
+ let(:object) { abstract_type }
18
+
19
+ specify { expect { subject }.to raise_error(NotImplementedError, "#{object} is an abstract type") }
20
+ end
21
+ 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
@@ -0,0 +1,208 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
4
+
5
+ # original code by Ashley Moran:
6
+ # http://aviewfromafar.net/2007/11/1/rake-task-for-heckling-your-specs
7
+
8
+ begin
9
+ require 'pathname'
10
+ require 'heckle'
11
+ require 'mspec'
12
+ require 'mspec/utils/name_map'
13
+
14
+ SKIP_METHODS = %w[ blank_slate_method_added ].freeze
15
+
16
+ class NameMap
17
+ def file_name(method, constant)
18
+ map = MAP[method]
19
+ name = if map
20
+ map[constant] || map[:default]
21
+ else
22
+ method.gsub(/[?!=]\z/, '')
23
+ end
24
+ "#{name}_spec.rb"
25
+ end
26
+ end
27
+
28
+ namespace :metrics do
29
+ desc 'Heckle each module and class'
30
+ task :heckle => :coverage do
31
+ unless Ruby2Ruby::VERSION == '1.2.2'
32
+ raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
33
+ end
34
+
35
+ require 'abstract_type'
36
+
37
+ root_module_regexp = Regexp.union('AbstractType')
38
+
39
+ spec_dir = Pathname('spec/unit')
40
+
41
+ NameMap::MAP.each do |op, method|
42
+ next if method.kind_of?(Hash)
43
+ NameMap::MAP[op] = { :default => method }
44
+ end
45
+
46
+ aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } }
47
+ map = NameMap.new
48
+
49
+ heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] }
50
+ uncovered_methods = 0
51
+
52
+ ObjectSpace.each_object(Module) do |mod|
53
+ next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
54
+
55
+ spec_prefix = spec_dir.join(mod.name.underscore)
56
+
57
+ specs = []
58
+
59
+ # get the public class methods
60
+ metaclass = class << mod; self end
61
+ ancestors = metaclass.ancestors
62
+
63
+ spec_class_methods = mod.singleton_methods(false)
64
+
65
+ spec_class_methods.reject! do |method|
66
+ %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s)
67
+ end
68
+
69
+ if mod.ancestors.include?(Singleton)
70
+ spec_class_methods.reject! { |method| method.to_s == 'instance' }
71
+ end
72
+
73
+ # get the protected and private class methods
74
+ other_class_methods = metaclass.protected_instance_methods(false) |
75
+ metaclass.private_instance_methods(false)
76
+
77
+ ancestors.each do |ancestor|
78
+ other_class_methods -= ancestor.protected_instance_methods(false) |
79
+ ancestor.private_instance_methods(false)
80
+ end
81
+
82
+ other_class_methods.reject! do |method|
83
+ method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s)
84
+ end
85
+
86
+ other_class_methods.reject! do |method|
87
+ next unless spec_class_methods.any? { |specced| specced.to_s == $1 }
88
+
89
+ spec_class_methods << method
90
+ end
91
+
92
+ spec_class_methods -= other_class_methods
93
+
94
+ # get the instances methods
95
+ spec_methods = mod.public_instance_methods(false)
96
+
97
+ other_methods = mod.protected_instance_methods(false) |
98
+ mod.private_instance_methods(false)
99
+
100
+ other_methods.reject! do |method|
101
+ next unless spec_methods.any? { |specced| specced.to_s == $1 }
102
+
103
+ spec_methods << method
104
+ end
105
+
106
+ # map the class methods to spec files
107
+ spec_class_methods.each do |method|
108
+ method = aliases[mod.name][method]
109
+ next if SKIP_METHODS.include?(method.to_s)
110
+
111
+ spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name))
112
+
113
+ unless spec_file.file?
114
+ raise "No spec file #{spec_file} for #{mod}.#{method}"
115
+ end
116
+
117
+ specs << [ ".#{method}", [ spec_file ] ]
118
+ end
119
+
120
+ # map the instance methods to spec files
121
+ spec_methods.each do |method|
122
+ method = aliases[mod.name][method]
123
+ next if SKIP_METHODS.include?(method.to_s)
124
+
125
+ spec_file = spec_prefix.join(map.file_name(method, mod.name))
126
+
127
+ unless spec_file.file?
128
+ raise "No spec file #{spec_file} for #{mod}##{method}"
129
+ end
130
+
131
+ specs << [ "##{method}", [ spec_file ] ]
132
+ end
133
+
134
+ # non-public methods are considered covered if they can be mutated
135
+ # and any spec fails for the current or descendant modules
136
+ other_methods.each do |method|
137
+ descedant_specs = []
138
+
139
+ ObjectSpace.each_object(Module) do |descedant|
140
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
141
+ descedant_spec_prefix = spec_dir.join(descedant.name.underscore)
142
+ descedant_specs << descedant_spec_prefix
143
+
144
+ if method.to_s == 'initialize'
145
+ descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb')))
146
+ end
147
+ end
148
+
149
+ specs << [ "##{method}", descedant_specs ]
150
+ end
151
+
152
+ other_class_methods.each do |method|
153
+ descedant_specs = []
154
+
155
+ ObjectSpace.each_object(Module) do |descedant|
156
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
157
+ descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods')
158
+ end
159
+
160
+ specs << [ ".#{method}", descedant_specs ]
161
+ end
162
+
163
+ specs.sort.each do |(method, spec_files)|
164
+ puts "Heckling #{mod}#{method}"
165
+ IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe|
166
+ while line = pipe.gets
167
+ case line = line.chomp
168
+ when "The following mutations didn't cause test failures:"
169
+ heckle_caught_modules[mod.name] << method
170
+ uncovered_methods += 1
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ if uncovered_methods > 0
178
+ error_message_lines = [ "*************\n" ]
179
+
180
+ error_message_lines << "Heckle found #{uncovered_methods} " \
181
+ "method#{"s" unless uncovered_methods == 1} " \
182
+ "where mutations didn't cause spec violations\n"
183
+
184
+ heckle_caught_modules.each do |mod, methods|
185
+ error_message_lines << "#{mod} contains the following " \
186
+ 'poorly-specified methods:'
187
+ methods.each do |method|
188
+ error_message_lines << " - #{method}"
189
+ end
190
+ error_message_lines << ''
191
+ end
192
+
193
+ error_message_lines << 'Get your act together and come back ' \
194
+ 'when your specs are doing their job!'
195
+
196
+ raise error_message_lines.join("\n")
197
+ else
198
+ puts 'Well done! Your code withstood a heckling.'
199
+ end
200
+ end
201
+ end
202
+ rescue LoadError
203
+ namespace :metrics do
204
+ task :heckle => :coverage do
205
+ $stderr.puts 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec'
206
+ end
207
+ end
208
+ end