adamantium 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.
Files changed (44) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +86 -0
  4. data/Gemfile +54 -0
  5. data/Guardfile +18 -0
  6. data/LICENSE +21 -0
  7. data/README.md +90 -0
  8. data/Rakefile +9 -0
  9. data/TODO +1 -0
  10. data/adamantium.gemspec +25 -0
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/roodi.yml +18 -0
  14. data/config/site.reek +91 -0
  15. data/config/yardstick.yml +2 -0
  16. data/lib/adamantium.rb +249 -0
  17. data/lib/adamantium/version.rb +3 -0
  18. data/spec/rcov.opts +7 -0
  19. data/spec/shared/command_method_behavior.rb +7 -0
  20. data/spec/shared/each_method_behaviour.rb +15 -0
  21. data/spec/shared/hash_method_behavior.rb +17 -0
  22. data/spec/shared/idempotent_method_behavior.rb +7 -0
  23. data/spec/shared/invertible_method_behaviour.rb +9 -0
  24. data/spec/spec_helper.rb +11 -0
  25. data/spec/unit/adamantium/class_methods/freeze_object_spec.rb +57 -0
  26. data/spec/unit/adamantium/class_methods/new_spec.rb +14 -0
  27. data/spec/unit/adamantium/dup_spec.rb +13 -0
  28. data/spec/unit/adamantium/fixtures/classes.rb +28 -0
  29. data/spec/unit/adamantium/freeze_spec.rb +51 -0
  30. data/spec/unit/adamantium/memoize_spec.rb +57 -0
  31. data/spec/unit/adamantium/memoized_spec.rb +29 -0
  32. data/spec/unit/adamantium/module_methods/included_spec.rb +16 -0
  33. data/spec/unit/adamantium/module_methods/memoize_spec.rb +88 -0
  34. data/tasks/metrics/ci.rake +7 -0
  35. data/tasks/metrics/flay.rake +47 -0
  36. data/tasks/metrics/flog.rake +43 -0
  37. data/tasks/metrics/heckle.rake +208 -0
  38. data/tasks/metrics/metric_fu.rake +29 -0
  39. data/tasks/metrics/reek.rake +15 -0
  40. data/tasks/metrics/roodi.rake +15 -0
  41. data/tasks/metrics/yardstick.rake +23 -0
  42. data/tasks/spec.rake +45 -0
  43. data/tasks/yard.rake +9 -0
  44. metadata +174 -0
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium, '#freeze' do
7
+ subject { object.freeze }
8
+
9
+ let(:described_class) { Class.new(AdamantiumSpecs::Object) }
10
+
11
+ before do
12
+ described_class.memoize(:test)
13
+ end
14
+
15
+ context 'with an unfrozen object' do
16
+ let(:object) { described_class.allocate }
17
+
18
+ it_should_behave_like 'a command method'
19
+
20
+ it 'freezes the object' do
21
+ expect { subject }.to change(object, :frozen?).
22
+ from(false).
23
+ to(true)
24
+ end
25
+
26
+ it 'sets a memoization instance variable' do
27
+ object.should_not be_instance_variable_defined(:@__memory)
28
+ subject
29
+ object.instance_variable_get(:@__memory).should be_instance_of(Adamantium::Memory)
30
+ end
31
+ end
32
+
33
+ context 'with a frozen object' do
34
+ let(:object) { described_class.new }
35
+
36
+ it_should_behave_like 'a command method'
37
+
38
+ it 'does not change the frozen state of the object' do
39
+ expect { subject }.to_not change(object, :frozen?)
40
+ end
41
+
42
+ it 'does not change the memoization instance variable' do
43
+ expect { subject }.to_not change { object.instance_variable_get(:@__memory) }
44
+ end
45
+
46
+ it 'does not set an instance variable for memoization' do
47
+ object.instance_variable_get(:@__memory).should be_instance_of(Adamantium::Memory)
48
+ subject
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium, '#memoize' do
7
+ subject { object.memoize(method, value) }
8
+
9
+ let(:described_class) { Class.new(AdamantiumSpecs::Object) }
10
+ let(:object) { described_class.new }
11
+ let(:method) { :test }
12
+
13
+ before do
14
+ described_class.memoize(method)
15
+ end
16
+
17
+ context 'when the value is frozen' do
18
+ let(:value) { String.new.freeze }
19
+
20
+ it 'sets the memoized value for the method to the value' do
21
+ subject
22
+ object.send(method).should equal(value)
23
+ end
24
+
25
+ it 'creates a method that returns a frozen value' do
26
+ subject
27
+ object.send(method).should be_frozen
28
+ end
29
+ end
30
+
31
+ context 'when the value is not frozen' do
32
+ let(:value) { String.new }
33
+
34
+ it 'sets the memoized value for the method to the value' do
35
+ subject
36
+ object.send(method).should eql(value)
37
+ end
38
+
39
+ it 'creates a method that returns a frozen value' do
40
+ subject
41
+ object.send(method).should be_frozen
42
+ end
43
+ end
44
+
45
+ context 'when the method is already memoized' do
46
+ let(:value) { stub }
47
+ let(:original) { nil }
48
+
49
+ before do
50
+ object.memoize(method, original)
51
+ end
52
+
53
+ it 'does not change the value' do
54
+ expect { subject }.to_not change { object.send(method) }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium, '#memoized' do
7
+ subject { object.memoized(method) }
8
+
9
+ let(:described_class) { Class.new(AdamantiumSpecs::Object) }
10
+ let(:method) { :test }
11
+ let(:value) { String.new.freeze }
12
+ let(:object) { described_class.new }
13
+
14
+ before do
15
+ described_class.memoize(method)
16
+ end
17
+
18
+ context 'when a method is memoized' do
19
+ before do
20
+ object.memoize(method, value)
21
+ end
22
+
23
+ it { should equal(value) }
24
+ end
25
+
26
+ context 'when a method is not memoized' do
27
+ it { should be_nil }
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium::ModuleMethods, '#included' do
7
+ subject { object.included(object) }
8
+
9
+ let(:object) { AdamantiumSpecs::Object }
10
+
11
+ before do
12
+ Adamantium.should_receive(:included).with(object).and_return(Adamantium)
13
+ end
14
+
15
+ it_should_behave_like 'a command method'
16
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ shared_examples_for 'memoizes method' do
7
+ it 'memoizes the instance method' do
8
+ subject
9
+ instance = object.new
10
+ instance.send(method).should equal(instance.send(method))
11
+ end
12
+
13
+ it 'creates a method that returns a frozen value' do
14
+ subject
15
+ object.new.send(method).should be_frozen
16
+ end
17
+
18
+ specification = proc do
19
+ subject
20
+ file, line = object.new.send(method).first.split(':')[0, 2]
21
+ File.expand_path(file).should eql(File.expand_path('../../../../../lib/adamantium.rb', __FILE__))
22
+ line.to_i.should eql(211)
23
+ end
24
+
25
+ it 'sets the file and line number properly' do
26
+ if RUBY_PLATFORM.include?('java')
27
+ pending('Kernel#caller returns the incorrect line number in JRuby', &specification)
28
+ else
29
+ instance_eval(&specification)
30
+ end
31
+ end
32
+
33
+ context 'when the initializer calls the memoized method' do
34
+ before do
35
+ method = self.method
36
+ object.send(:define_method, :initialize) { send(method) }
37
+ end
38
+
39
+ it 'allows the memoized method to be called within the initializer' do
40
+ subject
41
+ expect { object.new }.to_not raise_error(NoMethodError)
42
+ end
43
+
44
+ it 'memoizes the methdod inside the initializer' do
45
+ subject
46
+ object.new.memoized(method).should_not be_nil
47
+ end
48
+ end
49
+ end
50
+
51
+ describe Adamantium::ModuleMethods, '#memoize' do
52
+ subject { object.memoize(method) }
53
+
54
+ let(:object) { Class.new(AdamantiumSpecs::Object) }
55
+
56
+ context 'public method' do
57
+ let(:method) { :public_method }
58
+
59
+ it_should_behave_like 'a command method'
60
+ it_should_behave_like 'memoizes method'
61
+
62
+ it 'is still a public method' do
63
+ should be_public_method_defined(method)
64
+ end
65
+ end
66
+
67
+ context 'protected method' do
68
+ let(:method) { :protected_method }
69
+
70
+ it_should_behave_like 'a command method'
71
+ it_should_behave_like 'memoizes method'
72
+
73
+ it 'is still a protected method' do
74
+ should be_protected_method_defined(method)
75
+ end
76
+ end
77
+
78
+ context 'private method' do
79
+ let(:method) { :private_method }
80
+
81
+ it_should_behave_like 'a command method'
82
+ it_should_behave_like 'memoizes method'
83
+
84
+ it 'is still a private method' do
85
+ should be_private_method_defined(method)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,7 @@
1
+ desc 'Run metrics with Heckle'
2
+ task :ci => %w[ ci:metrics heckle ]
3
+
4
+ namespace :ci do
5
+ desc 'Run metrics'
6
+ task :metrics => %w[ verify_measurements flog flay reek roodi metrics:all ]
7
+ end
@@ -0,0 +1,47 @@
1
+ begin
2
+ if RUBY_VERSION == '1.8.7'
3
+ require 'flay'
4
+ require 'yaml'
5
+
6
+ config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze
7
+ threshold = config.fetch('threshold').to_i
8
+ total_score = config.fetch('total_score').to_f
9
+ files = Flay.expand_dirs_to_files(config.fetch('path', 'lib'))
10
+
11
+ # original code by Marty Andrews:
12
+ # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
13
+ desc 'Analyze for code duplication'
14
+ task :flay do
15
+ # run flay once without a threshold to ensure the max mass matches the threshold
16
+ flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
17
+ flay.process(*files)
18
+
19
+ max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max
20
+ unless max >= threshold
21
+ raise "Adjust flay threshold down to #{max}"
22
+ end
23
+
24
+ total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
25
+ unless total == total_score
26
+ raise "Flay total is now #{total}, but expected #{total_score}"
27
+ end
28
+
29
+ # run flay a second time with the threshold set
30
+ flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
31
+ flay.process(*files)
32
+
33
+ if flay.masses.any?
34
+ flay.report
35
+ raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
36
+ end
37
+ end
38
+ else
39
+ task :flay do
40
+ $stderr.puts 'Flay has inconsistend results accros ruby implementations. It is only enabled on 1.8.7, fix and remove guard'
41
+ end
42
+ end
43
+ rescue LoadError
44
+ task :flay do
45
+ abort 'Flay is not available. In order to run flay, you must: gem install flay'
46
+ end
47
+ end
@@ -0,0 +1,43 @@
1
+ begin
2
+ require 'flog'
3
+ require 'yaml'
4
+
5
+ class Float
6
+ def round_to(n)
7
+ (self * 10**n).round.to_f * 10**-n
8
+ end
9
+ end
10
+
11
+ config = YAML.load_file(File.expand_path('../../../config/flog.yml', __FILE__)).freeze
12
+ threshold = config.fetch('threshold').to_f.round_to(1)
13
+
14
+ # original code by Marty Andrews:
15
+ # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
16
+ desc 'Analyze for code complexity'
17
+ task :flog do
18
+ flog = Flog.new
19
+ flog.flog Array(config.fetch('path', 'lib'))
20
+
21
+ totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }.
22
+ map { |name, score| [ name, score.round_to(1) ] }.
23
+ sort_by { |name, score| score }
24
+
25
+ max = totals.last[1]
26
+ unless max >= threshold
27
+ raise "Adjust flog score down to #{max}"
28
+ end
29
+
30
+ bad_methods = totals.select { |name, score| score > threshold }
31
+ if bad_methods.any?
32
+ bad_methods.reverse_each do |name, score|
33
+ puts '%8.1f: %s' % [ score, name ]
34
+ end
35
+
36
+ raise "#{bad_methods.size} methods have a flog complexity > #{threshold}"
37
+ end
38
+ end
39
+ rescue LoadError
40
+ task :flog do
41
+ abort 'Flog is not available. In order to run flog, you must: gem install flog'
42
+ end
43
+ end
@@ -0,0 +1,208 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
2
+
3
+ # original code by Ashley Moran:
4
+ # http://aviewfromafar.net/2007/11/1/rake-task-for-heckling-your-specs
5
+
6
+ begin
7
+ require 'pathname'
8
+ require 'backports'
9
+ require 'active_support/inflector'
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.
23
+ gsub('?','_ques').
24
+ gsub('!','_bang').
25
+ gsub('=','_assign')
26
+ end
27
+ "#{name}_spec.rb"
28
+ end
29
+ end
30
+
31
+ desc 'Heckle each module and class'
32
+ task :heckle => :rcov do
33
+ unless Ruby2Ruby::VERSION == '1.2.2'
34
+ raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
35
+ end
36
+
37
+ require 'adamantium'
38
+
39
+ root_module_regexp = Regexp.union('Adamantium')
40
+
41
+ spec_dir = Pathname('spec/unit')
42
+
43
+ NameMap::MAP.each do |op, method|
44
+ next if method.kind_of?(Hash)
45
+ NameMap::MAP[op] = { :default => method }
46
+ end
47
+
48
+ aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } }
49
+ map = NameMap.new
50
+
51
+ heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] }
52
+ unhandled_mutations = 0
53
+
54
+ ObjectSpace.each_object(Module) do |mod|
55
+ next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
56
+
57
+ spec_prefix = spec_dir.join(mod.name.underscore)
58
+
59
+ specs = []
60
+
61
+ # get the public class methods
62
+ metaclass = class << mod; self end
63
+ ancestors = metaclass.ancestors
64
+
65
+ spec_class_methods = mod.singleton_methods(false)
66
+
67
+ spec_class_methods.reject! do |method|
68
+ %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s)
69
+ end
70
+
71
+ if mod.ancestors.include?(Singleton)
72
+ spec_class_methods.reject! { |method| method.to_s == 'instance' }
73
+ end
74
+
75
+ # get the protected and private class methods
76
+ other_class_methods = metaclass.protected_instance_methods(false) |
77
+ metaclass.private_instance_methods(false)
78
+
79
+ ancestors.each do |ancestor|
80
+ other_class_methods -= ancestor.protected_instance_methods(false) |
81
+ ancestor.private_instance_methods(false)
82
+ end
83
+
84
+ other_class_methods.reject! do |method|
85
+ method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s)
86
+ end
87
+
88
+ other_class_methods.reject! do |method|
89
+ next unless spec_class_methods.any? { |specced| specced.to_s == $1 }
90
+
91
+ spec_class_methods << method
92
+ end
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
+ next
116
+ end
117
+
118
+ specs << [ ".#{method}", [ spec_file ] ]
119
+ end
120
+
121
+ # map the instance methods to spec files
122
+ spec_methods.each do |method|
123
+ method = aliases[mod.name][method]
124
+ next if SKIP_METHODS.include?(method.to_s)
125
+
126
+ spec_file = spec_prefix.join(map.file_name(method, mod.name))
127
+
128
+ unless spec_file.file?
129
+ raise "No spec file #{spec_file} for #{mod}##{method}"
130
+ next
131
+ end
132
+
133
+ specs << [ "##{method}", [ spec_file ] ]
134
+ end
135
+
136
+ # non-public methods are considered covered if they can be mutated
137
+ # and any spec fails for the current or descendant modules
138
+ other_methods.each do |method|
139
+ descedant_specs = []
140
+
141
+ ObjectSpace.each_object(Module) do |descedant|
142
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
143
+ descedant_spec_prefix = spec_dir.join(descedant.name.underscore)
144
+ descedant_specs << descedant_spec_prefix
145
+
146
+ if method.to_s == 'initialize'
147
+ descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb')))
148
+ end
149
+ end
150
+
151
+ specs << [ "##{method}", descedant_specs ]
152
+ end
153
+
154
+ other_class_methods.each do |method|
155
+ descedant_specs = []
156
+
157
+ ObjectSpace.each_object(Module) do |descedant|
158
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
159
+ descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods')
160
+ end
161
+
162
+ specs << [ ".#{method}", descedant_specs ]
163
+ end
164
+
165
+ specs.sort.each do |(method, spec_files)|
166
+ puts "Heckling #{mod}#{method}"
167
+ IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe|
168
+ while line = pipe.gets
169
+ case line = line.chomp
170
+ when "The following mutations didn't cause test failures:"
171
+ heckle_caught_modules[mod.name] << method
172
+ when '+++ mutation'
173
+ unhandled_mutations += 1
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ if unhandled_mutations > 0
181
+ error_message_lines = [ "*************\n" ]
182
+
183
+ error_message_lines << "Heckle found #{unhandled_mutations} " \
184
+ "mutation#{"s" unless unhandled_mutations == 1} " \
185
+ "that didn't cause spec violations\n"
186
+
187
+ heckle_caught_modules.each do |mod, methods|
188
+ error_message_lines << "#{mod} contains the following " \
189
+ 'poorly-specified methods:'
190
+ methods.each do |method|
191
+ error_message_lines << " - #{method}"
192
+ end
193
+ error_message_lines << ''
194
+ end
195
+
196
+ error_message_lines << 'Get your act together and come back ' \
197
+ 'when your specs are doing their job!'
198
+
199
+ raise error_message_lines.join("\n")
200
+ else
201
+ puts 'Well done! Your code withstood a heckling.'
202
+ end
203
+ end
204
+ rescue LoadError
205
+ task :heckle => :spec do
206
+ $stderr.puts 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec'
207
+ end
208
+ end