virtus 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/.gitignore +33 -7
  2. data/.travis.yml +4 -74
  3. data/Changelog.md +11 -2
  4. data/Gemfile +10 -9
  5. data/README.md +85 -1
  6. data/TODO +18 -0
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/roodi.yml +3 -3
  10. data/config/site.reek +8 -3
  11. data/lib/virtus.rb +5 -0
  12. data/lib/virtus/attribute.rb +137 -30
  13. data/lib/virtus/attribute/array.rb +17 -1
  14. data/lib/virtus/attribute/boolean.rb +8 -13
  15. data/lib/virtus/attribute/collection.rb +96 -0
  16. data/lib/virtus/attribute/default_value.rb +11 -7
  17. data/lib/virtus/attribute/embedded_value.rb +70 -0
  18. data/lib/virtus/attribute/set.rb +25 -0
  19. data/lib/virtus/attribute_set.rb +18 -16
  20. data/lib/virtus/attributes_accessor.rb +66 -0
  21. data/lib/virtus/class_methods.rb +50 -28
  22. data/lib/virtus/coercion/array.rb +23 -0
  23. data/lib/virtus/coercion/string.rb +10 -4
  24. data/lib/virtus/instance_methods.rb +38 -31
  25. data/lib/virtus/support/options.rb +6 -8
  26. data/lib/virtus/support/type_lookup.rb +4 -11
  27. data/lib/virtus/version.rb +1 -1
  28. data/spec/integration/collection_member_coercion_spec.rb +75 -0
  29. data/spec/integration/custom_attributes_spec.rb +49 -0
  30. data/spec/integration/default_values_spec.rb +32 -0
  31. data/spec/integration/defining_attributes_spec.rb +79 -0
  32. data/spec/integration/embedded_value_spec.rb +50 -0
  33. data/spec/integration/overriding_virtus_spec.rb +46 -0
  34. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  35. data/spec/rcov.opts +1 -0
  36. data/spec/shared/constants_helpers.rb +9 -0
  37. data/spec/shared/options_class_method.rb +19 -0
  38. data/spec/spec_helper.rb +20 -7
  39. data/spec/unit/virtus/attribute/array/coerce_spec.rb +13 -0
  40. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +85 -0
  41. data/spec/unit/virtus/attribute/boolean/define_reader_method_spec.rb +15 -0
  42. data/spec/unit/virtus/attribute/boolean/value_coerced_spec.rb +97 -0
  43. data/spec/unit/virtus/attribute/boolean_spec.rb +2 -81
  44. data/spec/unit/virtus/attribute/class/coerce_spec.rb +13 -0
  45. data/spec/unit/virtus/attribute/class_methods/accessor_spec.rb +12 -0
  46. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +37 -0
  47. data/spec/unit/virtus/attribute/class_methods/coercion_method_spec.rb +9 -0
  48. data/spec/unit/virtus/attribute/class_methods/default_spec.rb +9 -0
  49. data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +31 -1
  50. data/spec/unit/virtus/attribute/class_methods/merge_options_spec.rb +11 -0
  51. data/spec/unit/virtus/attribute/class_methods/primitive_spec.rb +9 -0
  52. data/spec/unit/virtus/attribute/class_methods/reader_spec.rb +9 -0
  53. data/spec/unit/virtus/attribute/class_methods/writer_spec.rb +9 -0
  54. data/spec/unit/virtus/attribute/coerce_spec.rb +30 -0
  55. data/spec/unit/virtus/attribute/coercion_method_spec.rb +12 -0
  56. data/spec/unit/virtus/attribute/collection/class_methods/merge_options_spec.rb +40 -0
  57. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +26 -0
  58. data/spec/unit/virtus/attribute/date/coerce_spec.rb +47 -0
  59. data/spec/unit/virtus/attribute/date/value_coerced_spec.rb +46 -0
  60. data/spec/unit/virtus/attribute/date_time/coerce_spec.rb +68 -0
  61. data/spec/unit/virtus/attribute/decimal/coerce_spec.rb +117 -0
  62. data/spec/unit/virtus/attribute/default_spec.rb +32 -0
  63. data/spec/unit/virtus/attribute/default_value/attribute_spec.rb +11 -0
  64. data/spec/unit/virtus/attribute/default_value/class_methods/new_spec.rb +4 -2
  65. data/spec/unit/virtus/attribute/default_value/evaluate_spec.rb +51 -0
  66. data/spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb +9 -6
  67. data/spec/unit/virtus/attribute/default_value/value_spec.rb +11 -0
  68. data/spec/unit/virtus/attribute/define_accessor_methods_spec.rb +26 -0
  69. data/spec/unit/virtus/attribute/define_reader_method_spec.rb +24 -0
  70. data/spec/unit/virtus/attribute/define_writer_method_spec.rb +24 -0
  71. data/spec/unit/virtus/attribute/embedded_value/class_methods/merge_options_spec.rb +17 -0
  72. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +50 -0
  73. data/spec/unit/virtus/attribute/float/coerce_spec.rb +117 -0
  74. data/spec/unit/virtus/attribute/get_spec.rb +80 -0
  75. data/spec/unit/virtus/attribute/inspect_spec.rb +27 -0
  76. data/spec/unit/virtus/attribute/instance_variable_name_spec.rb +12 -0
  77. data/spec/unit/virtus/attribute/integer/coerce_spec.rb +105 -0
  78. data/spec/unit/virtus/attribute/name_spec.rb +12 -0
  79. data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +1 -1
  80. data/spec/unit/virtus/attribute/numeric/class_methods/max_spec.rb +9 -0
  81. data/spec/unit/virtus/attribute/numeric/class_methods/min_spec.rb +9 -0
  82. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +9 -7
  83. data/spec/unit/virtus/attribute/options_spec.rb +14 -0
  84. data/spec/unit/virtus/attribute/public_reader_spec.rb +24 -0
  85. data/spec/unit/virtus/attribute/public_writer_spec.rb +24 -0
  86. data/spec/unit/virtus/attribute/reader_visibility_spec.rb +24 -0
  87. data/spec/unit/virtus/attribute/set/coerce_spec.rb +13 -0
  88. data/spec/unit/virtus/attribute/set_spec.rb +49 -0
  89. data/spec/unit/virtus/attribute/string/coerce_spec.rb +11 -0
  90. data/spec/unit/virtus/attribute/time/coerce_spec.rb +67 -0
  91. data/spec/unit/virtus/attribute/value_coerced_spec.rb +19 -0
  92. data/spec/unit/virtus/attribute/writer_visibility_spec.rb +24 -0
  93. data/spec/unit/virtus/attribute_set/append_spec.rb +12 -0
  94. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +4 -0
  95. data/spec/unit/virtus/attribute_set/element_set_spec.rb +29 -9
  96. data/spec/unit/virtus/attributes_accessor/inspect_spec.rb +9 -0
  97. data/spec/unit/virtus/class_methods/attribute_spec.rb +23 -5
  98. data/spec/unit/virtus/class_methods/attributes_spec.rb +3 -5
  99. data/spec/unit/virtus/class_methods/const_missing_spec.rb +27 -0
  100. data/spec/unit/virtus/class_methods/inherited_spec.rb +21 -0
  101. data/spec/unit/virtus/coercion/array/to_set_spec.rb +12 -0
  102. data/spec/unit/virtus/coercion/date/class_methods/to_date_spec.rb +10 -0
  103. data/spec/unit/virtus/coercion/date_time/class_methods/to_datetime_spec.rb +10 -0
  104. data/spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb +10 -3
  105. data/spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb +10 -3
  106. data/spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb +10 -3
  107. data/spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb +8 -8
  108. data/spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb +2 -2
  109. data/spec/unit/virtus/coercion/string/class_methods/to_constant_spec.rb +1 -1
  110. data/spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb +1 -1
  111. data/spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb +13 -13
  112. data/spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb +25 -1
  113. data/spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb +25 -1
  114. data/spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb +30 -1
  115. data/spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb +13 -13
  116. data/spec/unit/virtus/coercion/time/class_methods/to_time_spec.rb +10 -0
  117. data/spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb +1 -1
  118. data/spec/unit/virtus/instance_methods/attributes_spec.rb +77 -20
  119. data/spec/unit/virtus/instance_methods/element_reference_spec.rb +1 -1
  120. data/spec/unit/virtus/instance_methods/element_set_spec.rb +2 -2
  121. data/spec/unit/virtus/instance_methods/to_hash_spec.rb +23 -0
  122. data/spec/unit/virtus/options/accept_options_spec.rb +10 -11
  123. data/spec/unit/virtus/options/accepted_options_spec.rb +1 -1
  124. data/spec/unit/virtus/options/options_spec.rb +27 -4
  125. data/tasks/metrics/ci.rake +2 -1
  126. data/tasks/metrics/heckle.rake +207 -0
  127. data/tasks/spec.rake +14 -7
  128. data/virtus.gemspec +1 -1
  129. metadata +111 -97
  130. data/VERSION +0 -1
  131. data/examples/custom_coercion_spec.rb +0 -50
  132. data/examples/default_values_spec.rb +0 -21
  133. data/examples/override_attribute_methods_spec.rb +0 -40
  134. data/spec/integration/virtus/attributes/attribute/set_spec.rb +0 -36
  135. data/spec/integration/virtus/class_methods/attribute_spec.rb +0 -82
  136. data/spec/integration/virtus/class_methods/attributes_spec.rb +0 -22
  137. data/spec/integration/virtus/class_methods/const_missing_spec.rb +0 -44
  138. data/spec/unit/shared/attribute.rb +0 -7
  139. data/spec/unit/shared/attribute/accept_options.rb +0 -37
  140. data/spec/unit/shared/attribute/accepted_options.rb +0 -5
  141. data/spec/unit/shared/attribute/get.rb +0 -44
  142. data/spec/unit/shared/attribute/inspect.rb +0 -7
  143. data/spec/unit/shared/attribute/set.rb +0 -37
  144. data/spec/unit/virtus/attribute/array_spec.rb +0 -24
  145. data/spec/unit/virtus/attribute/class_spec.rb +0 -24
  146. data/spec/unit/virtus/attribute/date_spec.rb +0 -59
  147. data/spec/unit/virtus/attribute/date_time_spec.rb +0 -87
  148. data/spec/unit/virtus/attribute/decimal_spec.rb +0 -109
  149. data/spec/unit/virtus/attribute/float_spec.rb +0 -109
  150. data/spec/unit/virtus/attribute/hash_spec.rb +0 -11
  151. data/spec/unit/virtus/attribute/integer_spec.rb +0 -99
  152. data/spec/unit/virtus/attribute/string_spec.rb +0 -21
  153. data/spec/unit/virtus/attribute/time_spec.rb +0 -82
  154. data/spec/unit/virtus/class_methods/new_spec.rb +0 -41
@@ -1,21 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
+ shared_examples_for 'a correct time object' do
4
+ it { should be_instance_of(Time) }
5
+
6
+ its(:year) { should == year }
7
+ its(:month) { should == month }
8
+ its(:day) { should == day }
9
+ its(:hour) { should == hour }
10
+ its(:min) { should == min }
11
+ its(:sec) { should == sec }
12
+ end
13
+
3
14
  describe Virtus::Coercion::String, '.to_time' do
4
15
  subject { object.to_time(string) }
5
16
 
6
17
  let(:object) { described_class }
7
18
 
8
- shared_examples_for 'a correct time object' do
9
- it { should be_instance_of(Time) }
10
-
11
- its(:year) { should == year }
12
- its(:month) { should == month }
13
- its(:day) { should == day }
14
- its(:hour) { should == hour }
15
- its(:min) { should == min }
16
- its(:sec) { should == sec }
17
- end
18
-
19
19
  context 'with a valid time string' do
20
20
  let(:year) { 2011 }
21
21
  let(:month) { 7 }
@@ -28,7 +28,7 @@ describe Virtus::Coercion::String, '.to_time' do
28
28
  let(:min) { 0 }
29
29
  let(:sec) { 0 }
30
30
 
31
- it_behaves_like 'a correct time object'
31
+ it_should_behave_like 'a correct time object'
32
32
  end
33
33
 
34
34
  context 'including time part' do
@@ -38,7 +38,7 @@ describe Virtus::Coercion::String, '.to_time' do
38
38
  let(:min) { 44 }
39
39
  let(:sec) { 50 }
40
40
 
41
- it_behaves_like 'a correct time object'
41
+ it_should_behave_like 'a correct time object'
42
42
  end
43
43
  end
44
44
 
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Coercion::Time, '.to_time' do
4
+ subject { object.to_time(time) }
5
+
6
+ let(:object) { described_class }
7
+ let(:time) { Time.local(2012, 1, 1) }
8
+
9
+ it { should equal(time) }
10
+ end
@@ -4,7 +4,7 @@ describe Virtus::Coercion::TrueClass, '.to_string' do
4
4
  subject { object.to_string(true_class) }
5
5
 
6
6
  let(:object) { described_class }
7
- let(:true_class) { true }
7
+ let(:true_class) { true }
8
8
 
9
9
  it { should be_instance_of(String) }
10
10
 
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- # TODO: split this into separate files - solnic
4
- describe Virtus do
3
+ describe Virtus::InstanceMethods do
5
4
  let(:model) do
6
5
  Class.new do
7
6
  include Virtus
@@ -9,36 +8,94 @@ describe Virtus do
9
8
  attribute :name, String
10
9
  attribute :age, Integer
11
10
  attribute :email, String, :accessor => :private
11
+
12
+ attr_accessor :non_attribute
12
13
  end
13
14
  end
14
15
 
15
- let(:object) do
16
- model.new(attributes)
17
- end
16
+ let(:attributes) { { :name => 'john', :age => 28 } }
17
+
18
+ describe '#attributes' do
19
+ subject { object.attributes }
18
20
 
19
- let(:attributes) do
20
- { :name => 'john', :age => 28 }
21
+ let(:object) { model.new(attributes) }
22
+
23
+ it { should be_instance_of(Hash) }
24
+
25
+ it { should eql(attributes) }
21
26
  end
22
27
 
23
- describe '#attributes' do
24
- it "returns a hash of attributes" do
25
- object.attributes.should eql(attributes)
28
+ describe '#attributes=' do
29
+ subject { object.attributes = attribute_values }
30
+
31
+ let(:object) { model.new }
32
+
33
+ context 'when given values for publicly writable attributes' do
34
+ let(:attribute_values) { attributes }
35
+
36
+ it { should be(attribute_values) }
37
+
38
+ it 'sets all provided attribute values' do
39
+ object.attributes.should eql(:age => nil, :name => nil)
40
+ subject
41
+ object.attributes.should eql(attributes)
42
+ end
26
43
  end
27
- end
28
44
 
29
- describe '#to_hash' do
30
- it 'returns attributes' do
31
- object.to_hash.should == object.attributes
45
+ context 'when given string keys for attributes' do
46
+ let(:attribute_values) { { 'name' => 'john', 'age' => 28 } }
47
+
48
+ it { should be(attribute_values) }
49
+
50
+ it 'sets values for publicly accessible attributes' do
51
+ object.attributes.should eql(:age => nil, :name => nil)
52
+ subject
53
+ object.attributes.should eql(attributes)
54
+ end
55
+ end
56
+
57
+ context 'when given values for privately writable attributes' do
58
+ let(:attribute_values) { attributes.merge(:email => 'john@domain.tld') }
59
+
60
+ it { should be(attribute_values) }
61
+
62
+ it 'only sets values for publicly accessible attributes' do
63
+ object.attributes.should eql(:age => nil, :name => nil)
64
+ subject
65
+ object.attributes.should eql(attributes)
66
+ end
32
67
  end
33
- end
34
68
 
35
- describe "#attributes=" do
36
- before do
37
- object.attributes = attributes
69
+ context 'when given values for non-attribute setters' do
70
+ let(:attribute_values) { attributes.merge(:non_attribute => 'foobar') }
71
+
72
+ it { should be(attribute_values) }
73
+
74
+ it 'sets values for publicly accessible attributes' do
75
+ object.attributes.should eql(:age => nil, :name => nil)
76
+ subject
77
+ object.attributes.should eql(attributes)
78
+ end
79
+
80
+ it 'silently ignores publicly accessible non-Virtus attributes' do
81
+ expect { subject }.to_not change { object.non_attribute }
82
+ end
38
83
  end
39
84
 
40
- it "sets attribute values for publicly accessible attributes" do
41
- object.attributes.should eql(attributes)
85
+ context "when given values that don't correspond to attributes" do
86
+ let(:attribute_values) { attributes.merge(:quux => 'foobar') }
87
+
88
+ it { should be(attribute_values) }
89
+
90
+ it 'sets values for publicly accessible attributes' do
91
+ object.attributes.should eql(:age => nil, :name => nil)
92
+ subject
93
+ object.attributes.should eql(attributes)
94
+ end
95
+
96
+ it 'silently ignores non-attribute keys' do
97
+ expect { subject }.to_not raise_exception
98
+ end
42
99
  end
43
100
  end
44
101
  end
@@ -18,7 +18,7 @@ describe Virtus::InstanceMethods, '#[]' do
18
18
  'john'
19
19
  end
20
20
 
21
- it "returns the value of an attribute" do
21
+ it 'returns the value of an attribute' do
22
22
  should eql(value)
23
23
  end
24
24
  end
@@ -18,11 +18,11 @@ describe Virtus::InstanceMethods, '#[]=' do
18
18
  'john'
19
19
  end
20
20
 
21
- it "returns the value" do
21
+ it 'returns the value' do
22
22
  should eql(value)
23
23
  end
24
24
 
25
- it "sets value of an attribute" do
25
+ it 'sets value of an attribute' do
26
26
  expect { subject }.to change { object.name }.from(nil).to(value)
27
27
  end
28
28
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::InstanceMethods, '#to_hash' do
4
+ subject { object.to_hash }
5
+
6
+ class Model
7
+ include Virtus
8
+
9
+ attribute :name, String
10
+ attribute :age, Integer
11
+ attribute :email, String, :accessor => :private
12
+ end
13
+
14
+ let(:model) { Model }
15
+ let(:object) { model.new(attributes) }
16
+ let(:attributes) { { :name => 'john', :age => 28 } }
17
+
18
+ it { should be_instance_of(Hash) }
19
+
20
+ it 'returns attributes' do
21
+ should eql(attributes)
22
+ end
23
+ end
@@ -1,15 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::Options, '.accept_options' do
4
- let(:object) do
5
- Class.new do
6
- extend Virtus::DescendantsTracker
7
- extend Virtus::Options
8
- end
3
+ describe Virtus::Options, '#accept_options' do
4
+ class Model
5
+ extend Virtus::DescendantsTracker
6
+ extend Virtus::Options
9
7
  end
10
8
 
9
+ let(:object) { Model }
11
10
  let(:descendant) { Class.new(object) }
12
- let(:new_option) { :width }
11
+ let(:new_option) { :width }
13
12
 
14
13
  specify { object.should respond_to(:accept_options) }
15
14
  specify { descendant.should respond_to(:accept_options) }
@@ -20,19 +19,19 @@ describe Virtus::Options, '.accept_options' do
20
19
  object.accept_options(new_option)
21
20
  end
22
21
 
23
- it "sets new accepted options on itself" do
22
+ it 'sets new accepted options on itself' do
24
23
  object.accepted_options.should include(new_option)
25
24
  end
26
25
 
27
- it "sets new accepted option on its descendants" do
26
+ it 'sets new accepted option on its descendants' do
28
27
  descendant.accepted_options.should include(new_option)
29
28
  end
30
29
 
31
- it "creates option accessors" do
30
+ it 'creates option accessors' do
32
31
  object.should respond_to(new_option)
33
32
  end
34
33
 
35
- it "creates option accessors on descendant" do
34
+ it 'creates option accessors on descendant' do
36
35
  descendant.should respond_to(new_option)
37
36
  end
38
37
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::Options, '.accepted_options' do
3
+ describe Virtus::Options, '#accepted_options' do
4
4
  subject { object.accepted_options }
5
5
 
6
6
  specify { object.should respond_to(:accepted_options) }
@@ -1,11 +1,34 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::Options, '.options' do
3
+ describe Virtus::Options, '#options' do
4
4
  subject { object.options }
5
5
 
6
- specify { object.should respond_to(:options) }
6
+ let(:object) do
7
+ Class.new do
8
+ extend Virtus::Options, Virtus::DescendantsTracker
9
+ end
10
+ end
7
11
 
8
- let(:object) { Class.new { extend Virtus::Options } }
12
+ context 'with an option that has a default value' do
13
+ let(:default_value) { stub('default_value') }
9
14
 
10
- it { should be_instance_of(Hash) }
15
+ before do
16
+ object.accept_options :name
17
+ object.name default_value
18
+ end
19
+
20
+ it { should be_instance_of(Hash) }
21
+
22
+ it { should eql(:name => default_value) }
23
+ end
24
+
25
+ context 'with an option that does not have a default value' do
26
+ before do
27
+ object.accept_options :name
28
+ end
29
+
30
+ it { should be_instance_of(Hash) }
31
+
32
+ it { should be_empty }
33
+ end
11
34
  end
@@ -1,4 +1,5 @@
1
- task :ci => %w[ ci:metrics ]
1
+ desc 'Run metrics with Heckle'
2
+ task :ci => %w[ ci:metrics heckle ]
2
3
 
3
4
  namespace :ci do
4
5
  desc 'Run metrics'
@@ -0,0 +1,207 @@
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.gsub(/[?!=]\z/, '')
23
+ end
24
+ "#{name}_spec.rb"
25
+ end
26
+ end
27
+
28
+ desc 'Heckle each module and class'
29
+ task :heckle => :rcov do
30
+ unless Ruby2Ruby::VERSION == '1.2.2'
31
+ raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
32
+ end
33
+
34
+ require 'virtus'
35
+
36
+ root_module_regexp = Regexp.union(
37
+ 'Virtus'
38
+ )
39
+
40
+ spec_dir = Pathname('spec/unit')
41
+
42
+ NameMap::MAP.each do |op, method|
43
+ next if method.kind_of?(Hash)
44
+ NameMap::MAP[op] = { :default => method }
45
+ end
46
+
47
+ aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } }
48
+ map = NameMap.new
49
+
50
+ heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] }
51
+ unhandled_mutations = 0
52
+
53
+ ObjectSpace.each_object(Module) do |mod|
54
+ next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
55
+
56
+ spec_prefix = spec_dir.join(mod.name.underscore)
57
+
58
+ specs = []
59
+
60
+ # get the public class methods
61
+ metaclass = class << mod; self end
62
+ ancestors = metaclass.ancestors
63
+
64
+ spec_class_methods = mod.singleton_methods(false)
65
+
66
+ spec_class_methods.reject! do |method|
67
+ %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s)
68
+ end
69
+
70
+ if mod.ancestors.include?(Singleton)
71
+ spec_class_methods.reject! { |method| method.to_s == 'instance' }
72
+ end
73
+
74
+ # get the protected and private class methods
75
+ other_class_methods = metaclass.protected_instance_methods(false) |
76
+ metaclass.private_instance_methods(false)
77
+
78
+ ancestors.each do |ancestor|
79
+ other_class_methods -= ancestor.protected_instance_methods(false) |
80
+ ancestor.private_instance_methods(false)
81
+ end
82
+
83
+ other_class_methods.reject! do |method|
84
+ method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s)
85
+ end
86
+
87
+ other_class_methods.reject! do |method|
88
+ next unless spec_class_methods.any? { |specced| specced.to_s == $1 }
89
+
90
+ spec_class_methods << method
91
+ end
92
+
93
+ # get the instances methods
94
+ spec_methods = mod.public_instance_methods(false)
95
+
96
+ other_methods = mod.protected_instance_methods(false) |
97
+ mod.private_instance_methods(false)
98
+
99
+ other_methods.reject! do |method|
100
+ next unless spec_methods.any? { |specced| specced.to_s == $1 }
101
+
102
+ spec_methods << method
103
+ end
104
+
105
+ # map the class methods to spec files
106
+ spec_class_methods.each do |method|
107
+ method = aliases[mod.name][method]
108
+ next if SKIP_METHODS.include?(method.to_s)
109
+
110
+ spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name))
111
+
112
+ unless spec_file.file?
113
+ warn "No spec file #{spec_file} for #{mod}.#{method}"
114
+ next
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
+ warn "No spec file #{spec_file} for #{mod}##{method}"
129
+ next
130
+ end
131
+
132
+ specs << [ "##{method}", [ spec_file ] ]
133
+ end
134
+
135
+ # non-public methods are considered covered if they can be mutated
136
+ # and any spec fails for the current or descendant modules
137
+ other_methods.each do |method|
138
+ descedant_specs = []
139
+
140
+ ObjectSpace.each_object(Module) do |descedant|
141
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
142
+ descedant_spec_prefix = spec_dir.join(descedant.name.underscore)
143
+ descedant_specs << descedant_spec_prefix
144
+
145
+ if method.to_s == 'initialize'
146
+ descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb')))
147
+ end
148
+ end
149
+
150
+ specs << [ "##{method}", descedant_specs ]
151
+ end
152
+
153
+ other_class_methods.each do |method|
154
+ descedant_specs = []
155
+
156
+ ObjectSpace.each_object(Module) do |descedant|
157
+ next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
158
+ descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods')
159
+ end
160
+
161
+ specs << [ ".#{method}", descedant_specs ]
162
+ end
163
+
164
+ specs.sort.each do |(method, spec_files)|
165
+ puts "Heckling #{mod}#{method}"
166
+ IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe|
167
+ while line = pipe.gets
168
+ case line = line.chomp
169
+ when "The following mutations didn't cause test failures:"
170
+ heckle_caught_modules[mod.name] << method
171
+ when '+++ mutation'
172
+ unhandled_mutations += 1
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ if unhandled_mutations > 0
180
+ error_message_lines = [ "*************\n" ]
181
+
182
+ error_message_lines << "Heckle found #{unhandled_mutations} " \
183
+ "mutation#{"s" unless unhandled_mutations == 1} " \
184
+ "that didn't cause spec violations\n"
185
+
186
+ heckle_caught_modules.each do |mod, methods|
187
+ error_message_lines << "#{mod} contains the following " \
188
+ 'poorly-specified methods:'
189
+ methods.each do |method|
190
+ error_message_lines << " - #{method}"
191
+ end
192
+ error_message_lines << ''
193
+ end
194
+
195
+ error_message_lines << 'Get your act together and come back ' \
196
+ 'when your specs are doing their job!'
197
+
198
+ raise error_message_lines.join("\n")
199
+ else
200
+ puts 'Well done! Your code withstood a heckling.'
201
+ end
202
+ end
203
+ rescue LoadError
204
+ task :heckle do
205
+ abort 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec'
206
+ end
207
+ end