ice_nine 0.1.0
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/.pelusa.yml +9 -0
- data/.rvmrc +1 -0
- data/.travis.yml +17 -0
- data/.yardopts +1 -0
- data/Gemfile +32 -0
- data/LICENSE +20 -0
- data/README.md +54 -0
- data/Rakefile +9 -0
- data/TODO +3 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +16 -0
- data/config/site.reek +91 -0
- data/config/yardstick.yml +2 -0
- data/ice_nine.gemspec +21 -0
- data/lib/ice_nine.rb +65 -0
- data/lib/ice_nine/freezer.rb +95 -0
- data/lib/ice_nine/freezer/array.rb +26 -0
- data/lib/ice_nine/freezer/hash.rb +30 -0
- data/lib/ice_nine/freezer/no_freeze.rb +48 -0
- data/lib/ice_nine/freezer/range.rb +29 -0
- data/lib/ice_nine/freezer/struct.rb +29 -0
- data/lib/ice_nine/version.rb +5 -0
- data/spec/rcov.opts +7 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/ice_nine/class_methods/deep_freeze_spec.rb +193 -0
- data/spec/unit/ice_nine/freezer/array/class_methods/deep_freeze_spec.rb +27 -0
- data/spec/unit/ice_nine/freezer/class_methods/deep_freeze_spec.rb +30 -0
- data/spec/unit/ice_nine/freezer/class_methods/element_reference_spec.rb +85 -0
- data/spec/unit/ice_nine/freezer/false_class/class_methods/deep_freeze_spec.rb +22 -0
- data/spec/unit/ice_nine/freezer/hash/class_methods/deep_freeze_spec.rb +40 -0
- data/spec/unit/ice_nine/freezer/nil_class/class_methods/deep_freeze_spec.rb +22 -0
- data/spec/unit/ice_nine/freezer/no_freeze/class_methods/deep_freeze_spec.rb +19 -0
- data/spec/unit/ice_nine/freezer/numeric/class_methods/deep_freeze_spec.rb +24 -0
- data/spec/unit/ice_nine/freezer/range/class_methods/deep_freeze_spec.rb +30 -0
- data/spec/unit/ice_nine/freezer/struct/class_methods/deep_freeze_spec.rb +27 -0
- data/spec/unit/ice_nine/freezer/symbol/class_methods/deep_freeze_spec.rb +22 -0
- data/spec/unit/ice_nine/freezer/true_class/class_methods/deep_freeze_spec.rb +22 -0
- data/tasks/metrics/ci.rake +7 -0
- data/tasks/metrics/flay.rake +43 -0
- data/tasks/metrics/flog.rake +46 -0
- data/tasks/metrics/heckle.rake +210 -0
- data/tasks/metrics/metric_fu.rake +31 -0
- data/tasks/metrics/reek.rake +11 -0
- data/tasks/metrics/roodi.rake +17 -0
- data/tasks/metrics/yardstick.rake +25 -0
- data/tasks/spec.rake +46 -0
- data/tasks/yard.rake +11 -0
- metadata +150 -0
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer'
         | 
| 5 | 
            +
            require 'ice_nine/freezer/struct'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            describe IceNine::Freezer, '.[]' do
         | 
| 8 | 
            +
              subject { object[mod] }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              let(:object)  { described_class }
         | 
| 11 | 
            +
              let(:freezer) { object          }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              describe 'when the module matches a descendant' do
         | 
| 14 | 
            +
                let(:freezer) { Class.new(object) }
         | 
| 15 | 
            +
                let(:mod)     { Class             }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                before do
         | 
| 18 | 
            +
                  object.const_set(mod.name, freezer)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                after do
         | 
| 22 | 
            +
                  object.send(:remove_const, mod.name)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                it 'returns the freezer' do
         | 
| 26 | 
            +
                  should be(freezer)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              describe 'when the module matches a descendant inside a namespace' do
         | 
| 31 | 
            +
                let(:namespace) { Class.new(object) }
         | 
| 32 | 
            +
                let(:freezer)   { Class.new(object) }
         | 
| 33 | 
            +
                let(:mod)       { Application::User }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                before :all do
         | 
| 36 | 
            +
                  module ::Application
         | 
| 37 | 
            +
                    class User; end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                after :all do
         | 
| 42 | 
            +
                  ::Application.send(:remove_const, :User)
         | 
| 43 | 
            +
                  Object.send(:remove_const, :Application)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                before do
         | 
| 47 | 
            +
                  namespace.const_set(:User, freezer)
         | 
| 48 | 
            +
                  object.const_set(:Application, namespace)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                after do
         | 
| 52 | 
            +
                  namespace.send(:remove_const, :User)
         | 
| 53 | 
            +
                  object.send(:remove_const, :Application)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                it 'returns the freezer' do
         | 
| 57 | 
            +
                  should be(freezer)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              describe 'when the module is a struct' do
         | 
| 62 | 
            +
                let(:mod)     { Struct.new(:a)           }
         | 
| 63 | 
            +
                let(:freezer) { IceNine::Freezer::Struct }
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                it 'returns the freezer' do
         | 
| 66 | 
            +
                  should be(freezer)
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              describe 'when the module does not match a descendant' do
         | 
| 71 | 
            +
                let(:mod) { Object }
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                it 'returns the freezer' do
         | 
| 74 | 
            +
                  should be(freezer)
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              describe 'when the module is anonymous' do
         | 
| 79 | 
            +
                let(:mod) { Class.new }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                it 'returns the freezer' do
         | 
| 82 | 
            +
                  should be(freezer)
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::FalseClass, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a false object' do
         | 
| 12 | 
            +
                let(:value) { false }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'does not freeze the object' do
         | 
| 19 | 
            +
                  expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::Hash, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a Hash object' do
         | 
| 12 | 
            +
                let(:value) { { Object.new => Object.new } }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'freezes the object' do
         | 
| 19 | 
            +
                  expect { subject }.should change(value, :frozen?).from(false).to(true)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it 'freezes each key in the Hash' do
         | 
| 23 | 
            +
                  subject.keys.select(&:frozen?).should == subject.keys
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                it 'freezes each value in the Hash' do
         | 
| 27 | 
            +
                  subject.values.select(&:frozen?).should == subject.values
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                if RUBY_VERSION >= '1.9' and RUBY_ENGINE == 'rbx'
         | 
| 31 | 
            +
                  it 'does not freeze the Hash state' do
         | 
| 32 | 
            +
                    subject.instance_variable_get(:@state).should_not be_frozen
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  it 'does not freeze the Hash entries' do
         | 
| 36 | 
            +
                    subject.instance_variable_get(:@entries).should_not be_frozen
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::NilClass, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a nil object' do
         | 
| 12 | 
            +
                let(:value) { nil }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'does not freeze the object' do
         | 
| 19 | 
            +
                  expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::NoFreeze, '.deep_freeze' do
         | 
| 7 | 
            +
               subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
              let(:value)  { stub('value')   }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              it 'returns the object' do
         | 
| 13 | 
            +
                should be(value)
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              it 'does not freeze the object' do
         | 
| 17 | 
            +
                expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::Numeric, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              [ 0.0, 0, 0x7fffffffffffffff ].each do |value|
         | 
| 12 | 
            +
                context "with a #{value.class} object" do
         | 
| 13 | 
            +
                  let(:value) { value }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  it 'returns the object' do
         | 
| 16 | 
            +
                    should be(value)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  it 'does not freeze the object' do
         | 
| 20 | 
            +
                    expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::Range, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a Range' do
         | 
| 12 | 
            +
                let(:value) { 'a'..'z' }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'freezes the object' do
         | 
| 19 | 
            +
                  expect { subject }.should change(value, :frozen?).from(false).to(true)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it 'freeze the first object in the Range' do
         | 
| 23 | 
            +
                  subject.begin.should be_frozen
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                it 'freeze the last object in the Range' do
         | 
| 27 | 
            +
                  subject.end.should be_frozen
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::Struct, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a Struct' do
         | 
| 12 | 
            +
                let(:value) { klass.new('1') }
         | 
| 13 | 
            +
                let(:klass) { Struct.new(:a) }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                it 'returns the object' do
         | 
| 16 | 
            +
                  should be(value)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                it 'freezes the object' do
         | 
| 20 | 
            +
                  expect { subject }.should change(value, :frozen?).from(false).to(true)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it 'freezes each value in the Struct' do
         | 
| 24 | 
            +
                  subject.values.select(&:frozen?).should == subject.values
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::Symbol, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a Symbol object' do
         | 
| 12 | 
            +
                let(:value) { :symbol }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'does not freeze the object' do
         | 
| 19 | 
            +
                  expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'spec_helper'
         | 
| 4 | 
            +
            require 'ice_nine/freezer/no_freeze'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe IceNine::Freezer::TrueClass, '.deep_freeze' do
         | 
| 7 | 
            +
              subject { object.deep_freeze(value) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:object) { described_class }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a true object' do
         | 
| 12 | 
            +
                let(:value) { true }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it 'returns the object' do
         | 
| 15 | 
            +
                  should be(value)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'does not freeze the object' do
         | 
| 19 | 
            +
                  expect { subject }.should_not change(value, :frozen?).from(false)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 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'))
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              # original code by Marty Andrews:
         | 
| 13 | 
            +
              # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
         | 
| 14 | 
            +
              desc 'Analyze for code duplication'
         | 
| 15 | 
            +
              task :flay do
         | 
| 16 | 
            +
                # run flay once without a threshold to ensure the max mass matches the threshold
         | 
| 17 | 
            +
                flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
         | 
| 18 | 
            +
                flay.process(*files)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max
         | 
| 21 | 
            +
                unless max.nil? || max >= threshold
         | 
| 22 | 
            +
                  raise "Adjust flay threshold down to #{max}"
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
         | 
| 26 | 
            +
                unless total == total_score
         | 
| 27 | 
            +
                  raise "Flay total is now #{total}, but expected #{total_score}"
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # run flay a second time with the threshold set
         | 
| 31 | 
            +
                flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
         | 
| 32 | 
            +
                flay.process(*files)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                if flay.masses.any?
         | 
| 35 | 
            +
                  flay.report
         | 
| 36 | 
            +
                  raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            rescue LoadError
         | 
| 40 | 
            +
              task :flay do
         | 
| 41 | 
            +
                abort 'Flay is not available. In order to run flay, you must: gem install flay'
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 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 | 
            +
              # original code by Marty Andrews:
         | 
| 17 | 
            +
              # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
         | 
| 18 | 
            +
              desc 'Analyze for code complexity'
         | 
| 19 | 
            +
              task :flog do
         | 
| 20 | 
            +
                flog = Flog.new
         | 
| 21 | 
            +
                flog.flog Array(config.fetch('path', 'lib'))
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                totals = flog.totals.select  { |name, score| name[-5, 5] != '#none'      }.
         | 
| 24 | 
            +
                                     map     { |name, score| [ name, score.round_to(1) ] }.
         | 
| 25 | 
            +
                                     sort_by { |name, score| score                       }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                last = totals.last
         | 
| 28 | 
            +
                max  = last[1] if last
         | 
| 29 | 
            +
                unless max.nil? || max >= threshold
         | 
| 30 | 
            +
                  raise "Adjust flog score down to #{max}"
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                bad_methods = totals.select { |name, score| score > threshold }
         | 
| 34 | 
            +
                if bad_methods.any?
         | 
| 35 | 
            +
                  bad_methods.reverse_each do |name, score|
         | 
| 36 | 
            +
                    puts '%8.1f: %s' % [ score, name ]
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  raise "#{bad_methods.size} methods have a flog complexity > #{threshold}"
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            rescue LoadError
         | 
| 43 | 
            +
              task :flog do
         | 
| 44 | 
            +
                abort 'Flog is not available. In order to run flog, you must: gem install flog'
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -0,0 +1,210 @@ | |
| 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 'active_support/inflector'
         | 
| 11 | 
            +
              require 'heckle'
         | 
| 12 | 
            +
              require 'mspec'
         | 
| 13 | 
            +
              require 'mspec/utils/name_map'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              SKIP_METHODS = %w[ blank_slate_method_added ].freeze
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              class NameMap
         | 
| 18 | 
            +
                def file_name(method, constant)
         | 
| 19 | 
            +
                  map  = MAP[method]
         | 
| 20 | 
            +
                  name = if map
         | 
| 21 | 
            +
                    map[constant] || map[:default]
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    method.gsub(/[?!=]\z/, '')
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  "#{name}_spec.rb"
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              desc 'Heckle each module and class'
         | 
| 30 | 
            +
              task :heckle => :rcov 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 'ice_nine'
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                root_module_regexp = Regexp.union(
         | 
| 38 | 
            +
                  'IceNine'
         | 
| 39 | 
            +
                )
         | 
| 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 | 
            +
                  spec_class_methods -= other_class_methods
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  # get the instances methods
         | 
| 97 | 
            +
                  spec_methods = mod.public_instance_methods(false)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  other_methods = mod.protected_instance_methods(false) |
         | 
| 100 | 
            +
                                  mod.private_instance_methods(false)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  other_methods.reject! do |method|
         | 
| 103 | 
            +
                    next unless spec_methods.any? { |specced| specced.to_s == $1 }
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    spec_methods << method
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  # map the class methods to spec files
         | 
| 109 | 
            +
                  spec_class_methods.each do |method|
         | 
| 110 | 
            +
                    method = aliases[mod.name][method]
         | 
| 111 | 
            +
                    next if SKIP_METHODS.include?(method.to_s)
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name))
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    unless spec_file.file?
         | 
| 116 | 
            +
                      raise "No spec file #{spec_file} for #{mod}.#{method}"
         | 
| 117 | 
            +
                      next
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    specs << [ ".#{method}", [ spec_file ] ]
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  # map the instance methods to spec files
         | 
| 124 | 
            +
                  spec_methods.each do |method|
         | 
| 125 | 
            +
                    method = aliases[mod.name][method]
         | 
| 126 | 
            +
                    next if SKIP_METHODS.include?(method.to_s)
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    spec_file = spec_prefix.join(map.file_name(method, mod.name))
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    unless spec_file.file?
         | 
| 131 | 
            +
                      raise "No spec file #{spec_file} for #{mod}##{method}"
         | 
| 132 | 
            +
                      next
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    specs << [ "##{method}", [ spec_file ] ]
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  # non-public methods are considered covered if they can be mutated
         | 
| 139 | 
            +
                  # and any spec fails for the current or descendant modules
         | 
| 140 | 
            +
                  other_methods.each do |method|
         | 
| 141 | 
            +
                    descedant_specs = []
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    ObjectSpace.each_object(Module) do |descedant|
         | 
| 144 | 
            +
                      next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
         | 
| 145 | 
            +
                      descedant_spec_prefix = spec_dir.join(descedant.name.underscore)
         | 
| 146 | 
            +
                      descedant_specs << descedant_spec_prefix
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                      if method.to_s == 'initialize'
         | 
| 149 | 
            +
                        descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb')))
         | 
| 150 | 
            +
                      end
         | 
| 151 | 
            +
                    end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    specs << [ "##{method}", descedant_specs ]
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  other_class_methods.each do |method|
         | 
| 157 | 
            +
                    descedant_specs = []
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    ObjectSpace.each_object(Module) do |descedant|
         | 
| 160 | 
            +
                      next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
         | 
| 161 | 
            +
                      descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods')
         | 
| 162 | 
            +
                    end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    specs << [ ".#{method}", descedant_specs ]
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  specs.sort.each do |(method, spec_files)|
         | 
| 168 | 
            +
                    puts "Heckling #{mod}#{method}"
         | 
| 169 | 
            +
                    IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe|
         | 
| 170 | 
            +
                      while line = pipe.gets
         | 
| 171 | 
            +
                        case line = line.chomp
         | 
| 172 | 
            +
                          when "The following mutations didn't cause test failures:"
         | 
| 173 | 
            +
                            heckle_caught_modules[mod.name] << method
         | 
| 174 | 
            +
                          when '+++ mutation'
         | 
| 175 | 
            +
                            unhandled_mutations += 1
         | 
| 176 | 
            +
                        end
         | 
| 177 | 
            +
                      end
         | 
| 178 | 
            +
                    end
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                if unhandled_mutations > 0
         | 
| 183 | 
            +
                  error_message_lines = [ "*************\n" ]
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                  error_message_lines << "Heckle found #{unhandled_mutations} " \
         | 
| 186 | 
            +
                    "mutation#{"s" unless unhandled_mutations == 1} " \
         | 
| 187 | 
            +
                    "that didn't cause spec violations\n"
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  heckle_caught_modules.each do |mod, methods|
         | 
| 190 | 
            +
                    error_message_lines << "#{mod} contains the following " \
         | 
| 191 | 
            +
                      'poorly-specified methods:'
         | 
| 192 | 
            +
                    methods.each do |method|
         | 
| 193 | 
            +
                      error_message_lines << " - #{method}"
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
                    error_message_lines << ''
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  error_message_lines << 'Get your act together and come back ' \
         | 
| 199 | 
            +
                    'when your specs are doing their job!'
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  raise error_message_lines.join("\n")
         | 
| 202 | 
            +
                else
         | 
| 203 | 
            +
                  puts 'Well done! Your code withstood a heckling.'
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
            rescue LoadError => e
         | 
| 207 | 
            +
              task :heckle do
         | 
| 208 | 
            +
                abort 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec' << e
         | 
| 209 | 
            +
              end
         | 
| 210 | 
            +
            end
         |