abstract_type 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.
@@ -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