virtus 0.0.10 → 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.
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