accord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +7 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +43 -0
  5. data/accord.gemspec +20 -0
  6. data/lib/accord.rb +4 -0
  7. data/lib/accord/adapter_registry.rb +106 -0
  8. data/lib/accord/base_registry.rb +73 -0
  9. data/lib/accord/declarations.rb +131 -0
  10. data/lib/accord/exceptions.rb +22 -0
  11. data/lib/accord/extendor.rb +36 -0
  12. data/lib/accord/extendor_container.rb +43 -0
  13. data/lib/accord/interface.rb +189 -0
  14. data/lib/accord/interface_body.rb +73 -0
  15. data/lib/accord/interface_members.rb +31 -0
  16. data/lib/accord/interface_method.rb +27 -0
  17. data/lib/accord/interfaces.rb +471 -0
  18. data/lib/accord/nested_key_hash.rb +66 -0
  19. data/lib/accord/ro.rb +65 -0
  20. data/lib/accord/signature_info.rb +76 -0
  21. data/lib/accord/specification.rb +83 -0
  22. data/lib/accord/subscription_registry.rb +76 -0
  23. data/lib/accord/tags.rb +25 -0
  24. data/lib/accord/version.rb +3 -0
  25. data/spec/adapter_registry_spec.rb +296 -0
  26. data/spec/declarations_spec.rb +144 -0
  27. data/spec/extendor_container_spec.rb +101 -0
  28. data/spec/extendor_spec.rb +203 -0
  29. data/spec/integration/adaptation_spec.rb +86 -0
  30. data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
  31. data/spec/integration/adapter_hook_spec.rb +41 -0
  32. data/spec/integration/default_adapters_spec.rb +81 -0
  33. data/spec/integration/hash_adapters_spec.rb +20 -0
  34. data/spec/integration/interface_declaration_spec.rb +258 -0
  35. data/spec/integration/multi_adapters_spec.rb +83 -0
  36. data/spec/integration/named_adapters_spec.rb +54 -0
  37. data/spec/integration/single_adapters_spec.rb +93 -0
  38. data/spec/integration/subscriptions_spec.rb +245 -0
  39. data/spec/integration/verification_spec.rb +215 -0
  40. data/spec/interface_body_spec.rb +157 -0
  41. data/spec/interface_members_spec.rb +57 -0
  42. data/spec/interface_spec.rb +147 -0
  43. data/spec/nested_key_hash_spec.rb +140 -0
  44. data/spec/signature_info_spec.rb +65 -0
  45. data/spec/spec_helper.rb +2 -0
  46. data/spec/specification_spec.rb +246 -0
  47. data/spec/subscription_registry_spec.rb +206 -0
  48. data/spec/tags_spec.rb +38 -0
  49. metadata +134 -0
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'accord/signature_info'
3
+
4
+ module Accord
5
+ describe SignatureInfo do
6
+ subject { SignatureInfo.new }
7
+
8
+ describe "#param" do
9
+ it "adds new arguments to #arguments" do
10
+ subject.param(:foo)
11
+ expect(subject.arguments).to eq [ { name: :foo } ]
12
+ end
13
+
14
+ it "converts names to symbols" do
15
+ subject.param('foo')
16
+ expect(subject.arguments).to eq [ { name: :foo } ]
17
+ end
18
+
19
+ it "accepts a unitary hash which holds a default value" do
20
+ subject.param(foo: :bar)
21
+ expect(subject.arguments).to eq [ { name: :foo, default: :bar } ]
22
+ end
23
+
24
+ it "rejects anything else" do
25
+ expect { subject.param(1) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+
29
+ describe "#splat" do
30
+ context "with a symbol" do
31
+ it "adds a splat argument" do
32
+ subject.splat(:foo)
33
+ expect(subject.arguments).to eq [ { name: :foo, splat: true } ]
34
+ end
35
+ end
36
+
37
+ context "with a string" do
38
+ it "sets a splat argument, as a symbol" do
39
+ subject.splat('foo')
40
+ expect(subject.arguments).to eq [ { name: :foo, splat: true } ]
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "#block" do
46
+ it "returns nil if no splat argument has been set" do
47
+ expect(subject.block).to be_nil
48
+ end
49
+
50
+ context "with a symbol" do
51
+ it "sets a block argument" do
52
+ subject.block(:foo)
53
+ expect(subject.block).to eq :foo
54
+ end
55
+ end
56
+
57
+ context "with a string" do
58
+ it "sets a block argument, as a symbol" do
59
+ subject.block('foo')
60
+ expect(subject.block).to eq :foo
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+ require 'set'
3
+ require 'accord/specification'
4
+
5
+ module Accord
6
+ describe Specification do
7
+ describe "empty specification" do
8
+ let(:spec) { Specification.new }
9
+ it "has only itself as its ancestor" do
10
+ expect(spec.ancestors).to eq [spec]
11
+ end
12
+
13
+ it "extends itself" do
14
+ expect(spec).to be_extends(spec)
15
+ end
16
+ end
17
+
18
+ describe "specification based on another" do
19
+ let(:base_spec) { Specification.new }
20
+ let(:spec) { Specification.new([base_spec]) }
21
+
22
+ it "has itself as and its base specification as ancestors" do
23
+ expect(Set.new(spec.ancestors)).to eq Set.new([spec, base_spec])
24
+ end
25
+
26
+ it "extends itself" do
27
+ expect(spec).to be_extends(spec)
28
+ end
29
+
30
+ it "extends the base specification" do
31
+ expect(spec).to be_extends(base_spec)
32
+ end
33
+ end
34
+
35
+ describe "specification ancestry ordering" do
36
+ # Note that spec ancestors are not sorted like Ruby's module ancestors.
37
+ #
38
+ # The expected results should match the C3 algorithm. For more
39
+ # information, refer to the MRO used in Python (introduced in Python
40
+ # 2.3): http://www.python.org/download/releases/2.3/mro/
41
+
42
+ let(:root) { Specification.new(:root) }
43
+
44
+ describe "impossible" do
45
+ let(:a) { Specification.new([root]) }
46
+ let(:b) { Specification.new([a, root]) }
47
+ let(:c) { Specification.new([root]) }
48
+ let(:d) { Specification.new([b, c]) }
49
+ # let(:s5) { Specification.new([c, b, d]) } # This should break
50
+
51
+ it "rejects such hierarchy" do
52
+ # This hierarchy is rejected because local order prececedence cannot
53
+ # be satisfied without violating monotonicity.
54
+ expect { Specification.new([c, b, d]) }.to raise_error(TypeError)
55
+ end
56
+ end
57
+
58
+ describe "possible" do
59
+ let(:a) { Specification.new(:a, [root]) }
60
+ let(:b) { Specification.new(:b, [root]) }
61
+ let(:c) { Specification.new(:c, [root]) }
62
+ let(:d) { Specification.new(:d, [c, a]) }
63
+ let(:e) { Specification.new(:e, [c, b]) }
64
+ let(:f) { Specification.new(:f, [e, d]) }
65
+
66
+ let(:expected_resolution_order) { [f, e, d, c, b, a, root] }
67
+
68
+ subject { f }
69
+ its(:ancestors) { should eq expected_resolution_order }
70
+ end
71
+
72
+ describe "possible" do
73
+ let(:a) { Specification.new(:a, [root]) }
74
+ let(:b) { Specification.new(:b, [root]) }
75
+ let(:c) { Specification.new(:c, [root]) }
76
+ let(:d) { Specification.new(:d, [c, a]) }
77
+ let(:e) { Specification.new(:e, [b, c]) }
78
+ let(:f) { Specification.new(:f, [e, d]) }
79
+
80
+ let(:expected_resolution_order) { [f, e, b, d, c, a, root] }
81
+
82
+ subject { f }
83
+ its(:ancestors) { should eq expected_resolution_order }
84
+ end
85
+
86
+ describe "possible" do
87
+ let(:a) { Specification.new(:a, [root]) }
88
+ let(:b) { Specification.new(:b, [root]) }
89
+ let(:c) { Specification.new(:c, [root]) }
90
+ let(:d) { Specification.new(:d, [root]) }
91
+ let(:e) { Specification.new(:e, [root]) }
92
+ let(:k1) { Specification.new(:k1, [a, b, c]) }
93
+ let(:k2) { Specification.new(:k2, [d, b, e]) }
94
+ let(:k3) { Specification.new(:k3, [d, a]) }
95
+ let(:z) { Specification.new(:z, [k1, k2, k3]) }
96
+
97
+ let(:expected_resolution_order) do
98
+ [z, k1, k2, k3, d, a, b, c, e, root]
99
+ end
100
+
101
+ subject { z }
102
+ its(:ancestors) { should eq expected_resolution_order }
103
+ end
104
+ end
105
+
106
+ describe "bad bases" do
107
+ context "on construction" do
108
+ it "complains if something other than Specification is set as a base" do
109
+ expect { Specification.new(['bad']) }.to raise_error(TypeError)
110
+ end
111
+ end
112
+
113
+ context "on #bases=" do
114
+ let(:spec) { Specification.new }
115
+
116
+ it "complains if set to not an array of Specification's" do
117
+ expect { spec.bases = ['bad'] }.to raise_error(TypeError)
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "bases change" do
123
+ let(:spec) { Specification.new }
124
+ let(:dependent_spec) { Specification.new([spec]) }
125
+
126
+ it "updates ancestry in dependent specifications" do
127
+ base = Specification.new
128
+ spec.bases = [base]
129
+
130
+ expect(dependent_spec.ancestors).to eq [dependent_spec, spec, base]
131
+ end
132
+
133
+ it "updates ancestry in dependent specs if base change in ancestor" do
134
+ base1 = Specification.new
135
+ base2 = Specification.new([base1])
136
+ spec.bases = [base2]
137
+
138
+ base2.bases = []
139
+
140
+ expect(spec.ancestors).to eq [spec, base2]
141
+ end
142
+
143
+ it "makes old bases to not be extended by old dependent" do
144
+ old_base = Specification.new
145
+ new_base = Specification.new
146
+ spec.bases = [old_base]
147
+
148
+ spec.bases = [new_base]
149
+
150
+ expect(spec).to_not be_extends(old_base)
151
+ end
152
+
153
+ it "can leave no base" do
154
+ base = Specification.new
155
+ spec.bases = [base]
156
+
157
+ spec.bases = []
158
+
159
+ expect(spec.ancestors).to eq [spec]
160
+ end
161
+
162
+ it "can add bases" do
163
+ base1 = Specification.new
164
+ base2 = Specification.new
165
+ spec.bases = [base1]
166
+
167
+ spec.bases = [base1, base2]
168
+
169
+ expect(spec.ancestors).to eq [spec, base1, base2]
170
+ end
171
+
172
+ it "leaves other dependents untouched" do
173
+ base = Specification.new
174
+ spec.bases = [base]
175
+ other_spec = Specification.new([base])
176
+
177
+ spec.bases = []
178
+ expect(other_spec.ancestors).to include(base)
179
+ end
180
+
181
+ it "cannot modify ancestors through modifying returned bases directly" do
182
+ base = Specification.new
183
+ spec.bases << base
184
+
185
+ expect(spec.ancestors).to eq [spec]
186
+ end
187
+ end
188
+
189
+ describe "#interfaces" do
190
+ it "is empty by default" do
191
+ expect(Specification.new.interfaces).to be_empty
192
+ end
193
+
194
+ it "is empty when dealing only with pure specifications" do
195
+ base = Specification.new
196
+ spec = Specification.new([base])
197
+ expect(spec.interfaces).to be_empty
198
+ end
199
+
200
+ it "takes interfaces from bases through #each_interface" do
201
+ interface1 = stub(:i1)
202
+ interface2 = stub(:i2)
203
+
204
+ base1 = Specification.new
205
+ base1.stub(:each_interface).and_yield(interface1)
206
+
207
+ base2 = Specification.new
208
+ base2.stub(:each_interface).and_yield(interface2)
209
+
210
+ spec = Specification.new([base1, base2])
211
+
212
+ expect(spec.interfaces).to eq [interface1, interface2]
213
+ end
214
+
215
+ it "avoids duplications" do
216
+ interface = stub(:i)
217
+
218
+ base1 = Specification.new
219
+ base1.stub(:each_interface).and_yield(interface)
220
+
221
+ base2 = Specification.new
222
+ base2.stub(:each_interface).and_yield(interface)
223
+
224
+ spec = Specification.new([base1, base2])
225
+
226
+ expect(spec.interfaces).to eq [interface]
227
+ end
228
+
229
+ it "accumulates interfaces from bases' bases" do
230
+ interface1 = stub(:i1)
231
+ interface2 = stub(:i2)
232
+
233
+ base1 = Specification.new
234
+ base1.stub(:each_interface).and_yield(interface1)
235
+
236
+ base2 = Specification.new
237
+ base2.stub(:each_interface).and_yield(interface2)
238
+
239
+ base3 = Specification.new([base2])
240
+ spec = Specification.new([base1, base3])
241
+
242
+ expect(spec.interfaces).to eq [interface1, interface2]
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,206 @@
1
+ require 'spec_helper'
2
+ require 'accord/subscription_registry'
3
+
4
+ module Accord
5
+ describe SubscriptionRegistry do
6
+ subject(:registry) { SubscriptionRegistry.new }
7
+
8
+ let(:subscriber) { Proc.new {} }
9
+ let(:other_subscriber) { Proc.new {} }
10
+
11
+ let(:i1) { stub_interface }
12
+ let(:i2) { stub_interface }
13
+
14
+ def stub_interface(*bases)
15
+ stub.tap do |interface|
16
+ interface.stub(
17
+ :iro => [interface],
18
+ :ancestors => [interface]
19
+ )
20
+ interface.stub(:extends?).and_return(false)
21
+ interface.stub(:extends?).with(interface).and_return(true)
22
+ bases.each do |base|
23
+ interface.iro << base
24
+ interface.ancestors << base
25
+ interface.stub(:extends?).with(base).and_return(true)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#all" do
31
+ it "returns no adapter when registry is empty" do
32
+ expect(subject.all).to be_empty
33
+ end
34
+
35
+ context "subscribed most simple single subscriber" do
36
+ it "returns that if no arguments are passed" do
37
+ subject.subscribe([Interface], Interface, &subscriber)
38
+ expect(subject.all).to eq [subscriber]
39
+ end
40
+ end
41
+
42
+ it "hits right subscriber if matches" do
43
+ subject.subscribe([i1], nil, &subscriber)
44
+ expect(
45
+ subject.all(required: [i1], provided: nil)
46
+ ).to eq [subscriber]
47
+ end
48
+
49
+ it "miss subscriber if none matches" do
50
+ subject.subscribe([i1], nil, &subscriber)
51
+ expect(
52
+ subject.all(required: [i2], provided: nil)
53
+ ).to be_empty
54
+ end
55
+ end
56
+
57
+ describe "#subscribe" do
58
+ it "complains if subscription happens without block" do
59
+ expect {
60
+ subject.subscribe([Interface], Interface, '')
61
+ }.to raise_error(ArgumentError)
62
+ end
63
+
64
+ it "allows subscribing a null subscriber" do
65
+ subject.subscribe([], i1, &subscriber)
66
+ expect(subject.all(required: [], provided: i1)).to eq [subscriber]
67
+ end
68
+
69
+ it "subscribes multiple subscribers" do
70
+ subject.subscribe([nil], nil, &subscriber)
71
+ subject.subscribe([nil], nil, &other_subscriber)
72
+ expect(
73
+ subject.all(required: [Interface], provided: Interface)
74
+ ).to eq [subscriber, other_subscriber]
75
+ end
76
+
77
+ context "when using [nil] and nil for required and provided" do
78
+ it "defaults to most simple single subscriber" do
79
+ subject.subscribe([nil], nil, &subscriber)
80
+ expect(
81
+ subject.all(required: [Interface], provided: Interface)
82
+ ).to eq [subscriber]
83
+ end
84
+ end
85
+
86
+ context "when using nil and nil for required and provided" do
87
+ it "defaults to most simple single subscriber" do
88
+ subject.subscribe(nil, nil, &subscriber)
89
+ expect(
90
+ subject.all(required: [Interface], provided: Interface)
91
+ ).to eq [subscriber]
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#lookup" do
97
+ it "returns no subscriber when registry is empty" do
98
+ expect(subject.lookup([], nil)).to be_empty
99
+ end
100
+
101
+ context "registered most simple single subscriber" do
102
+ before do
103
+ subject.subscribe([Interface], Interface, &subscriber)
104
+ end
105
+
106
+ it "returns that if required is nil and provided is nil" do
107
+ expect(subject.lookup(nil, nil)).to eq [subscriber]
108
+ end
109
+
110
+ it "returns that if required is [nil] and provided is nil" do
111
+ expect(subject.lookup([nil], nil)).to eq [subscriber]
112
+ end
113
+ end
114
+
115
+ it "hits right subscriber if matches exactly" do
116
+ subject.subscribe([i1], nil, &subscriber)
117
+ expect(subject.lookup([i1], nil)).to eq [subscriber]
118
+ end
119
+
120
+ it "hits right subscriber if required extends some registered" do
121
+ i12 = stub_interface(i1)
122
+ subject.subscribe([i1], nil, &subscriber)
123
+ expect(subject.lookup([i12], nil)).to eq [subscriber]
124
+ end
125
+
126
+ it "miss subscriber if none matches" do
127
+ subject.subscribe([i1], nil, &subscriber)
128
+ expect(subject.lookup([i2], nil)).to be_empty
129
+ end
130
+ end
131
+
132
+ describe "#unsubscribe" do
133
+ it "doesn't complain when unsubscribing empty registry" do
134
+ expect { subject.unsubscribe([nil], nil) }.to_not raise_error
135
+ end
136
+
137
+ it "keeps subscribed if required doesn't match on unsubscription" do
138
+ subject.subscribe([i1], nil, &subscriber)
139
+ subject.unsubscribe([i2], nil)
140
+ expect(
141
+ subject.all(required: [i1], provided: nil)
142
+ ).to eq [subscriber]
143
+ end
144
+
145
+ it "keeps subscribed if provided doesn't match on unsubscription" do
146
+ subject.subscribe([nil], i1, &subscriber)
147
+ subject.unsubscribe([nil], i2)
148
+ expect(
149
+ subject.all(required: [nil], provided: i1)
150
+ ).to eq [subscriber]
151
+ end
152
+
153
+ it "keeps subscribed if value is passed but doesn't match on "\
154
+ "unsubscription" do
155
+ subject.subscribe([i1], nil, &subscriber)
156
+ subject.unsubscribe([i1], nil, stub)
157
+ expect(
158
+ subject.all(required: [i1], provided: nil)
159
+ ).to eq [subscriber]
160
+ end
161
+
162
+ it "unsubscribes only subscribers matching the value passed" do
163
+ subject.subscribe([i1], nil, &subscriber)
164
+ subject.subscribe([i1], nil, &other_subscriber)
165
+ subject.unsubscribe([i1], nil, subscriber)
166
+ expect(
167
+ subject.all(required: [i1], provided: nil)
168
+ ).to eq [other_subscriber]
169
+ end
170
+
171
+ it "unsubscribes if value not passed and everything else matches "\
172
+ "previous subscription" do
173
+ subject.subscribe([i1], nil, &subscriber)
174
+ subject.unsubscribe([i1], nil)
175
+ expect(
176
+ subject.all(required: [i1], provided: nil)
177
+ ).to be_empty
178
+ end
179
+
180
+ it "unsubscribes all if value not passed" do
181
+ subject.subscribe([i1], nil, &subscriber)
182
+ subject.subscribe([i1], nil, &other_subscriber)
183
+ subject.unsubscribe([i1], nil)
184
+ expect(
185
+ subject.all(required: [i1], provided: nil, name: '')
186
+ ).to be_empty
187
+ end
188
+
189
+ context "when using [nil] and nil for required and provided" do
190
+ it "defaults to most simple single adapter" do
191
+ subject.subscribe([Interface], Interface, &subscriber)
192
+ subject.unsubscribe([nil], nil)
193
+ expect(subject.all).to be_empty
194
+ end
195
+ end
196
+
197
+ context "when using nil and nil for required and provided" do
198
+ it "defaults to most simple single adapter" do
199
+ subject.subscribe([Interface], Interface, &subscriber)
200
+ subject.unsubscribe(nil, nil)
201
+ expect(subject.all).to be_empty
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end