descendants_tracker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## Rubinius
17
+ *.rbc
18
+ .rbx
19
+
20
+ ## PROJECT::GENERAL
21
+ *.gem
22
+ coverage
23
+ profiling
24
+ turbulence
25
+ rdoc
26
+ pkg
27
+ tmp
28
+ doc
29
+ log
30
+ .yardoc
31
+ measurements
32
+
33
+ ## BUNDLER
34
+ .bundle
35
+ Gemfile.lock
36
+
37
+ ## PROJECT::SPECIFIC
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use @$(basename `pwd`) --create
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ before_install: gem install bundler
3
+ bundler_args: --without yard guard
4
+ script: "bundle exec rake spec"
5
+ rvm:
6
+ - 1.8.7
7
+ - 1.9.2
8
+ - 1.9.3
9
+ - jruby-18mode
10
+ - jruby-19mode
11
+ - rbx-18mode
12
+ - rbx-19mode
13
+ - ree
14
+ - ruby-head
15
+ - jruby-head
16
+ notifications:
17
+ email:
18
+ - dan.kubb@gmail.com
19
+ - mbj@seonic.net
data/Gemfile ADDED
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :yard do
8
+ gem 'yard', '~> 0.8.3'
9
+ gem 'redcarpet', '~> 2.2.2', :platforms => [ :mri, :rbx ]
10
+ end
11
+
12
+ group :guard do
13
+ gem 'guard', '~> 1.5.4'
14
+ gem 'guard-bundler', '~> 1.0.0'
15
+ gem 'guard-rspec', '~> 1.2.1'
16
+ end
17
+
18
+ group :benchmarks do
19
+ gem 'rbench', '~> 0.2.3'
20
+ end
21
+
22
+ platform :jruby do
23
+ group :jruby do
24
+ gem 'jruby-openssl', '~> 0.7.4'
25
+ end
26
+ end
27
+
28
+ group :metrics do
29
+ gem 'flay', '~> 1.4.3'
30
+ gem 'flog', '~> 2.5.3'
31
+ gem 'reek', '~> 1.2.8', :github => 'dkubb/reek'
32
+ gem 'roodi', '~> 2.1.0'
33
+ gem 'yardstick', '~> 0.8.0'
34
+
35
+ platforms :ruby_18, :ruby_19 do
36
+ # this indirectly depends on ffi which does not build on ruby-head
37
+ gem 'yard-spellcheck', '~> 0.1.5'
38
+ end
39
+
40
+ platforms :mri_18 do
41
+ gem 'arrayfields', '~> 4.7.4' # for metric_fu
42
+ gem 'fattr', '~> 2.2.0' # for metric_fu
43
+ gem 'heckle', '~> 1.4.3'
44
+ gem 'json', '~> 1.7.3' # for metric_fu rake task
45
+ gem 'map', '~> 6.2.0' # for metric_fu
46
+ gem 'metric_fu', '~> 2.1.1'
47
+ gem 'mspec', '~> 1.5.17'
48
+ gem 'rcov', '~> 1.0.0'
49
+ gem 'ruby2ruby', '= 1.2.2' # for heckle
50
+ end
51
+
52
+ platforms :ruby_19 do
53
+ gem 'simplecov', '~> 0.7.1'
54
+ end
55
+
56
+ platforms :rbx do
57
+ gem 'pelusa', '~> 0.2.1'
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ guard :bundler do
4
+ watch('Gemfile')
5
+ end
6
+
7
+ guard :rspec do
8
+ # run all specs if the spec_helper or supporting files files are modified
9
+ watch('spec/spec_helper.rb') { 'spec' }
10
+ watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' }
11
+
12
+ # run unit specs if associated lib code is modified
13
+ watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] }
14
+ watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' }
15
+
16
+ # run a spec if it is modified
17
+ watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z})
18
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009-2012 Dan Kubb
2
+ Copyright (c) 2012 Markus Schirp (packaging)
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,88 @@
1
+ descendants_tracker
2
+ ===================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/dkubb/descendants_tracker.png?branch=master)](http://travis-ci.org/dkubb/descendants_tracker)
5
+ [![Dependency Status](https://gemnasium.com/dkubb/descendants_tracker.png)](https://gemnasium.com/dkubb/descendants_tracker)
6
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/dkubb/descendants_tracker)
7
+
8
+ Small module to track descendants in an unobtrusive way.
9
+
10
+ Installation
11
+ ------------
12
+
13
+ With Rubygems:
14
+
15
+ ```bash
16
+ $ gem install descendants_tracker
17
+ $ irb -rubygems
18
+ >> require 'descendants_tracker'
19
+ => true
20
+ ```
21
+
22
+ With git and local working copy:
23
+
24
+ ```bash
25
+ $ git clone git://github.com/dkubb/descendants_tracker.git
26
+ $ cd descendants_tracker
27
+ $ rake install
28
+ $ irb -rubygems
29
+ >> require 'descendants_tracker'
30
+ => true
31
+ ```
32
+
33
+ Examples
34
+ --------
35
+
36
+ ``` ruby
37
+ class Foo
38
+ extend Virtus::DescendantsTracker
39
+ end
40
+
41
+ class Bar < Foo
42
+ end
43
+
44
+ Foo.descendants # => [Bar]
45
+ ```
46
+
47
+ Credits
48
+ -------
49
+
50
+ * Dan Kubb ([dkubb](https://github.com/dkubb))
51
+ * Piotr Solnica ([solnic](https://github.com/solnic))
52
+ * Markus Schirp ([mbj](https://github.com/mbj))
53
+
54
+ Contributing
55
+ -------------
56
+
57
+ * If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin.
58
+ * Fork the project.
59
+ * Make your feature addition or bug fix.
60
+ * Follow this [style guide](https://github.com/dkubb/styleguide).
61
+ * Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered.
62
+ * Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
63
+ * Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged.
64
+ * Send me a pull request. Bonus points for topic branc
65
+
66
+ License
67
+ -------
68
+
69
+ Copyright (c) 2011-2012 Dan Kubb
70
+
71
+ Permission is hereby granted, free of charge, to any person obtaining
72
+ a copy of this software and associated documentation files (the
73
+ "Software"), to deal in the Software without restriction, including
74
+ without limitation the rights to use, copy, modify, merge, publish,
75
+ distribute, sublicense, and/or sell copies of the Software, and to
76
+ permit persons to whom the Software is furnished to do so, subject to
77
+ the following conditions:
78
+
79
+ The above copyright notice and this permission notice shall be
80
+ included in all copies or substantial portions of the Software.
81
+
82
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
83
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
84
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
85
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
86
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
87
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
88
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake'
4
+
5
+ require File.expand_path('../lib/descendants_tracker/version', __FILE__)
6
+
7
+ FileList['tasks/**/*.rake'].each { |task| import task }
8
+
9
+ task :default => :spec
data/TODO ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ ---
2
+ threshold: 6
3
+ total_score: 8
@@ -0,0 +1,2 @@
1
+ ---
2
+ threshold: 6.5
@@ -0,0 +1,18 @@
1
+ ---
2
+ AbcMetricMethodCheck: { score: 10.3 }
3
+ AssignmentInConditionalCheck: { }
4
+ CaseMissingElseCheck: { }
5
+ ClassLineCountCheck: { line_count: 293 }
6
+ ClassNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
7
+ ClassVariableCheck: { }
8
+ CyclomaticComplexityBlockCheck: { complexity: 2 }
9
+ CyclomaticComplexityMethodCheck: { complexity: 4 }
10
+ EmptyRescueBodyCheck: { }
11
+ ForLoopCheck: { }
12
+ # TODO: decrease line_count to 5 to 10
13
+ MethodLineCountCheck: { line_count: 14 }
14
+ MethodNameCheck: { pattern: !ruby/regexp '/\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|[+*&|-])\z/' }
15
+ ModuleLineCountCheck: { line_count: 295 }
16
+ ModuleNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
17
+ # TODO: decrease parameter_count to 2 or less
18
+ ParameterNumberCheck: { parameter_count: 3 }
@@ -0,0 +1,91 @@
1
+ ---
2
+ UncommunicativeParameterName:
3
+ accept: []
4
+ exclude: []
5
+ enabled: true
6
+ reject:
7
+ - !ruby/regexp /^.$/
8
+ - !ruby/regexp /[0-9]$/
9
+ - !ruby/regexp /[A-Z]/
10
+ LargeClass:
11
+ max_methods: 10
12
+ exclude: []
13
+ enabled: true
14
+ max_instance_variables: 2
15
+ UncommunicativeMethodName:
16
+ accept: []
17
+ exclude: []
18
+ enabled: true
19
+ reject:
20
+ - !ruby/regexp /^[a-z]$/
21
+ - !ruby/regexp /[0-9]$/
22
+ - !ruby/regexp /[A-Z]/
23
+ LongParameterList:
24
+ max_params: 2 # TODO: decrease max_params to 2
25
+ exclude: []
26
+ enabled: true
27
+ overrides: {}
28
+ FeatureEnvy:
29
+ exclude: []
30
+ enabled: true
31
+ ClassVariable:
32
+ exclude: []
33
+ enabled: true
34
+ BooleanParameter:
35
+ exclude: []
36
+ enabled: true
37
+ IrresponsibleModule:
38
+ exclude: []
39
+ enabled: true
40
+ UncommunicativeModuleName:
41
+ accept: []
42
+ exclude: []
43
+ enabled: true
44
+ reject:
45
+ - !ruby/regexp /^.$/
46
+ - !ruby/regexp /[0-9]$/
47
+ NestedIterators:
48
+ ignore_iterators: []
49
+ exclude: []
50
+ enabled: true
51
+ max_allowed_nesting: 1
52
+ LongMethod:
53
+ max_statements: 7 # TODO: decrease max_statements to 5 or less
54
+ exclude: []
55
+ enabled: true
56
+ Duplication:
57
+ allow_calls: []
58
+ exclude: []
59
+ enabled: true
60
+ max_calls: 1
61
+ UtilityFunction:
62
+ max_helper_calls: 1
63
+ exclude: []
64
+ enabled: true
65
+ Attribute:
66
+ exclude: []
67
+ enabled: false
68
+ UncommunicativeVariableName:
69
+ accept: []
70
+ exclude: []
71
+ enabled: true
72
+ reject:
73
+ - !ruby/regexp /^.$/
74
+ - !ruby/regexp /[0-9]$/
75
+ - !ruby/regexp /[A-Z]/
76
+ SimulatedPolymorphism:
77
+ exclude: []
78
+ enabled: true
79
+ max_ifs: 1
80
+ DataClump:
81
+ exclude: []
82
+ enabled: true
83
+ max_copies: 1
84
+ min_clump_size: 3
85
+ ControlCouple:
86
+ exclude: []
87
+ enabled: true
88
+ LongYieldList:
89
+ max_params: 1
90
+ exclude: []
91
+ enabled: true
@@ -0,0 +1,2 @@
1
+ ---
2
+ threshold: 100
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/descendants_tracker/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'descendants_tracker'
7
+ gem.version = DescendantsTracker::VERSION.dup
8
+ gem.authors = [ 'Dan Kubb', 'Piotr Solnica', 'Markus Schirp' ]
9
+ gem.email = %w[ dan.kubb@gmail.com piotr.solnica@gmail.com mbj@seonic.net ]
10
+ gem.description = 'Module that adds descendant tracking to a class'
11
+ gem.summary = gem.description
12
+ gem.homepage = 'https://github.com/dkubb/descendants_tracker'
13
+
14
+ gem.require_paths = %w[lib]
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = `git ls-files -- {spec}/*`.split($/)
17
+ gem.extra_rdoc_files = %w[LICENSE README.md TODO]
18
+
19
+ gem.add_development_dependency('rake', '~> 10.0.2')
20
+ gem.add_development_dependency('rspec', '~> 1.3.2')
21
+ end
@@ -0,0 +1,41 @@
1
+ # Module that adds descendant tracking to a class
2
+ module DescendantsTracker
3
+
4
+ # Return the descendants of this class
5
+ #
6
+ # @return [Array<Class>]
7
+ #
8
+ # @api private
9
+ def descendants
10
+ @descendants ||= []
11
+ end
12
+
13
+ # Add the descendant to this class and the superclass
14
+ #
15
+ # @param [Class] descendant
16
+ #
17
+ # @return [self]
18
+ #
19
+ # @api private
20
+ def add_descendant(descendant)
21
+ superclass = self.superclass
22
+ superclass.add_descendant(descendant) if superclass.respond_to?(:add_descendant)
23
+ descendants.unshift(descendant)
24
+ self
25
+ end
26
+
27
+ private
28
+
29
+ # Hook called when class is inherited
30
+ #
31
+ # @param [Class] descendant
32
+ #
33
+ # @return [undefined]
34
+ #
35
+ # @api private
36
+ def inherited(descendant)
37
+ super
38
+ add_descendant(descendant)
39
+ end
40
+
41
+ end # module DescendantsTracker
@@ -0,0 +1,3 @@
1
+ module DescendantsTracker
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 'descendants_tracker'
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,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe DescendantsTracker, '#add_descendant' do
4
+ subject { object.add_descendant(descendant) }
5
+
6
+ let(:described_class) { Class.new { extend DescendantsTracker } }
7
+ let(:object) { Class.new(described_class) }
8
+ let(:descendant) { Class.new }
9
+
10
+ it { should equal(object) }
11
+
12
+ it 'prepends the class to the descendants' do
13
+ object.descendants << original = Class.new
14
+ expect { subject }.to change { object.descendants.dup }.
15
+ from([ original ]).
16
+ to([ descendant, original ])
17
+ end
18
+
19
+ it 'prepends the class to the superclass descendants' do
20
+ expect { subject }.to change { object.superclass.descendants.dup }.
21
+ from([ object ]).
22
+ to([ descendant, object ])
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe DescendantsTracker, '#descendants' do
4
+ subject { object.descendants }
5
+
6
+ let(:described_class) { Class.new { extend DescendantsTracker } }
7
+ let(:object) { described_class }
8
+
9
+ context 'when there are no descendants' do
10
+ it_should_behave_like 'an idempotent method'
11
+
12
+ it { should be_empty }
13
+ end
14
+
15
+ context 'when there are descendants' do
16
+ let!(:descendant) { Class.new(object) } # trigger the class inhertance
17
+
18
+ it_should_behave_like 'an idempotent method'
19
+
20
+ it { should eql([ descendant ]) }
21
+ end
22
+ 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 'descendants_tracker'
36
+
37
+ root_module_regexp = Regexp.union('DescendantsTracker')
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
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'metric_fu'
5
+ require 'json'
6
+
7
+ # XXX: temporary hack until metric_fu is fixed
8
+ MetricFu::Saikuro.class_eval { include FileUtils }
9
+
10
+ MetricFu::Configuration.run do |config|
11
+ config.rcov = {
12
+ :environment => 'test',
13
+ :test_files => %w[ spec/**/*_spec.rb ],
14
+ :rcov_opts => %w[
15
+ --sort coverage
16
+ --no-html
17
+ --text-coverage
18
+ --no-color
19
+ --profile
20
+ --exclude spec/,^/
21
+ --include lib:spec
22
+ ],
23
+ }
24
+ end
25
+ rescue LoadError
26
+ namespace :metrics do
27
+ task :all do
28
+ $stderr.puts 'metric_fu is not available. In order to run metrics:all, you must: gem install metric_fu'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'reek/rake/task'
5
+
6
+ RBX_18_MODE = RUBY_VERSION < '1.9' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
7
+
8
+ namespace :metrics do
9
+ Reek::Rake::Task.new do |t|
10
+ # reek has some problems under rbx in 1.8 mode that cause the underlying
11
+ # script to raise an exception. Rather than halt the "rake ci" process due
12
+ # to one bug, we choose to ignore it in this specific case until reek can be
13
+ # fixed.
14
+ t.fail_on_error = ! RBX_18_MODE # always true, except under rbx 18 mode
15
+ end
16
+ end
17
+ rescue LoadError
18
+ task :reek do
19
+ $stderr.puts 'Reek is not available. In order to run reek, you must: gem install reek'
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'roodi'
5
+ require 'rake/tasklib'
6
+ require 'roodi_task'
7
+
8
+ namespace :metrics do
9
+ RoodiTask.new do |t|
10
+ t.verbose = false
11
+ t.config = File.expand_path('../../../config/roodi.yml', __FILE__)
12
+ t.patterns = %w[ lib/**/*.rb ]
13
+ end
14
+ end
15
+ rescue LoadError
16
+ task :roodi do
17
+ $stderr.puts 'Roodi is not available. In order to run roodi, you must: gem install roodi'
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'yardstick/rake/measurement'
5
+ require 'yardstick/rake/verify'
6
+ require 'yaml'
7
+
8
+ config = YAML.load_file(File.expand_path('../../../config/yardstick.yml', __FILE__))
9
+
10
+ namespace :metrics do
11
+ # yardstick_measure task
12
+ Yardstick::Rake::Measurement.new
13
+
14
+ # verify_measurements task
15
+ Yardstick::Rake::Verify.new do |verify|
16
+ verify.threshold = config.fetch('threshold')
17
+ end
18
+ end
19
+ rescue LoadError
20
+ %w[ yardstick_measure verify_measurements ].each do |name|
21
+ task name.to_s do
22
+ $stderr.puts "Yardstick is not available. In order to run #{name}, you must: gem install yardstick"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ spec_defaults = lambda do |spec|
4
+ spec.ruby_opts = %w[ -r./spec/support/config_alias ]
5
+ spec.spec_opts << '--options' << 'spec/spec.opts'
6
+ end
7
+
8
+ begin
9
+ require 'spec/rake/spectask'
10
+
11
+ desc 'Run all specs'
12
+ task :spec => %w[ spec:unit spec:integration ]
13
+
14
+ namespace :spec do
15
+ desc 'Run unit specs'
16
+ Spec::Rake::SpecTask.new(:unit) do |unit|
17
+ spec_defaults.call(unit)
18
+ unit.pattern = 'spec/unit/**/*_spec.rb'
19
+ end
20
+
21
+ desc 'Run integration specs'
22
+ Spec::Rake::SpecTask.new(:integration) do |integration|
23
+ spec_defaults.call(integration)
24
+ integration.pattern = 'spec/integration/**/*_spec.rb'
25
+ end
26
+ end
27
+ rescue LoadError
28
+ %w[ spec spec:unit spec:integration ].each do |name|
29
+ task name do
30
+ $stderr.puts "rspec is not available. In order to run #{name}, you must: gem install rspec"
31
+ end
32
+ end
33
+ end
34
+
35
+ namespace :metrics do
36
+ begin
37
+ if RUBY_VERSION < '1.9'
38
+ desc 'Generate code coverage'
39
+ Spec::Rake::SpecTask.new(:coverage) do |rcov|
40
+ spec_defaults.call(rcov)
41
+ rcov.rcov = true
42
+ rcov.pattern = 'spec/unit/**/*_spec.rb'
43
+ rcov.rcov_opts = File.read('spec/rcov.opts').split(/\s+/)
44
+ end
45
+ else
46
+ desc 'Generate code coverage'
47
+ task :coverage do
48
+ ENV['COVERAGE'] = 'true'
49
+ Rake::Task['spec:unit'].execute
50
+ end
51
+ end
52
+ rescue LoadError
53
+ task :coverage do
54
+ lib = RUBY_VERSION < '1.9' ? 'rcov' : 'simplecov'
55
+ $stderr.puts "coverage is not available. In order to run #{lib}, you must: gem install #{lib}"
56
+ end
57
+ end
58
+ end
59
+
60
+ task :test => :spec
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'yard'
5
+
6
+ YARD::Rake::YardocTask.new
7
+ rescue LoadError
8
+ task :yard do
9
+ $stderr.puts 'YARD is not available. In order to run yard, you must: gem install yard'
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: descendants_tracker
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Dan Kubb
14
+ - Piotr Solnica
15
+ - Markus Schirp
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2012-11-24 00:00:00 Z
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rake
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 75
31
+ segments:
32
+ - 10
33
+ - 0
34
+ - 2
35
+ version: 10.0.2
36
+ type: :development
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 31
47
+ segments:
48
+ - 1
49
+ - 3
50
+ - 2
51
+ version: 1.3.2
52
+ type: :development
53
+ version_requirements: *id002
54
+ description: Module that adds descendant tracking to a class
55
+ email:
56
+ - dan.kubb@gmail.com
57
+ - piotr.solnica@gmail.com
58
+ - mbj@seonic.net
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ extra_rdoc_files:
64
+ - LICENSE
65
+ - README.md
66
+ - TODO
67
+ files:
68
+ - .gitignore
69
+ - .rvmrc
70
+ - .travis.yml
71
+ - Gemfile
72
+ - Guardfile
73
+ - LICENSE
74
+ - README.md
75
+ - Rakefile
76
+ - TODO
77
+ - config/flay.yml
78
+ - config/flog.yml
79
+ - config/roodi.yml
80
+ - config/site.reek
81
+ - config/yardstick.yml
82
+ - descendants_tracker.gemspec
83
+ - lib/descendants_tracker.rb
84
+ - lib/descendants_tracker/version.rb
85
+ - spec/rcov.opts
86
+ - spec/shared/command_method_behavior.rb
87
+ - spec/shared/each_method_behaviour.rb
88
+ - spec/shared/hash_method_behavior.rb
89
+ - spec/shared/idempotent_method_behavior.rb
90
+ - spec/shared/invertible_method_behaviour.rb
91
+ - spec/spec.opts
92
+ - spec/spec_helper.rb
93
+ - spec/support/config_alias.rb
94
+ - spec/unit/descendants_tracker/add_descendant_spec.rb
95
+ - spec/unit/descendants_tracker/descendants_spec.rb
96
+ - tasks/metrics/ci.rake
97
+ - tasks/metrics/flay.rake
98
+ - tasks/metrics/flog.rake
99
+ - tasks/metrics/heckle.rake
100
+ - tasks/metrics/metric_fu.rake
101
+ - tasks/metrics/reek.rake
102
+ - tasks/metrics/roodi.rake
103
+ - tasks/metrics/yardstick.rake
104
+ - tasks/spec.rake
105
+ - tasks/yard.rake
106
+ homepage: https://github.com/dkubb/descendants_tracker
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ requirements: []
133
+
134
+ rubyforge_project:
135
+ rubygems_version: 1.8.24
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Module that adds descendant tracking to a class
139
+ test_files: []
140
+
141
+ has_rdoc: