mini_kraken 0.1.01 → 0.1.02

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +0 -3
  4. data/lib/mini_kraken/core/any_value.rb +29 -0
  5. data/lib/mini_kraken/core/association.rb +21 -0
  6. data/lib/mini_kraken/core/association_walker.rb +179 -0
  7. data/lib/mini_kraken/core/atomic_term.rb +64 -0
  8. data/lib/mini_kraken/core/binary_relation.rb +61 -0
  9. data/lib/mini_kraken/core/composite_term.rb +54 -0
  10. data/lib/mini_kraken/core/cons_cell.rb +44 -0
  11. data/lib/mini_kraken/core/duck_fiber.rb +44 -0
  12. data/lib/mini_kraken/core/environment.rb +59 -0
  13. data/lib/mini_kraken/core/equals.rb +216 -0
  14. data/lib/mini_kraken/core/fail.rb +8 -5
  15. data/lib/mini_kraken/core/freshness.rb +42 -0
  16. data/lib/mini_kraken/core/goal.rb +31 -6
  17. data/lib/mini_kraken/core/k_integer.rb +15 -0
  18. data/lib/mini_kraken/core/k_symbol.rb +15 -0
  19. data/lib/mini_kraken/core/nullary_relation.rb +11 -3
  20. data/lib/mini_kraken/core/outcome.rb +35 -0
  21. data/lib/mini_kraken/core/relation.rb +31 -4
  22. data/lib/mini_kraken/core/succeed.rb +13 -1
  23. data/lib/mini_kraken/core/term.rb +7 -0
  24. data/lib/mini_kraken/core/variable.rb +45 -3
  25. data/lib/mini_kraken/core/variable_ref.rb +76 -0
  26. data/lib/mini_kraken/core/vocabulary.rb +161 -0
  27. data/lib/mini_kraken/glue/fresh_env.rb +31 -0
  28. data/lib/mini_kraken/glue/run_star_expression.rb +43 -0
  29. data/lib/mini_kraken/version.rb +1 -1
  30. data/spec/core/association_spec.rb +38 -0
  31. data/spec/core/association_walker_spec.rb +191 -0
  32. data/spec/core/cons_cell_spec.rb +63 -0
  33. data/spec/core/duck_fiber_spec.rb +62 -0
  34. data/spec/core/environment_spec.rb +154 -0
  35. data/spec/core/equals_spec.rb +289 -0
  36. data/spec/core/fail_spec.rb +16 -0
  37. data/spec/core/goal_spec.rb +36 -10
  38. data/spec/core/k_symbol_spec.rb +72 -0
  39. data/spec/core/succeed_spec.rb +43 -0
  40. data/spec/core/variable_ref_spec.rb +31 -0
  41. data/spec/core/variable_spec.rb +11 -3
  42. data/spec/core/vocabulary_spec.rb +188 -0
  43. data/spec/glue/fresh_env_spec.rb +36 -0
  44. data/spec/glue/run_star_expression_spec.rb +247 -0
  45. data/spec/support/factory_methods.rb +54 -0
  46. metadata +46 -13
  47. data/lib/mini_kraken/core/facade.rb +0 -45
  48. data/lib/mini_kraken/core/formal_arg.rb +0 -6
  49. data/lib/mini_kraken/core/publisher.rb +0 -27
  50. data/lib/mini_kraken/core/run_star_expression.rb +0 -34
  51. data/lib/mini_kraken/dsl/kraken_dsl.rb +0 -12
  52. data/spec/core/facade_spec.rb +0 -38
  53. data/spec/core/run_star_expression_spec.rb +0 -43
  54. data/spec/dsl/kraken_dsl_spec.rb +0 -31
@@ -0,0 +1,31 @@
1
+ require_relative '../core/environment'
2
+ require_relative '../core/variable'
3
+
4
+ module MiniKraken
5
+ module Glue
6
+ # (fresh (x) (== 'pea q))
7
+ # Introduces the new variable 'x'
8
+ # Takes a list of names and a goal-like object
9
+ # Must respond to message attain(aPublisher, vars) and must return an Outcome
10
+ class FreshEnv < Core::Environment
11
+ # @return [Goal]
12
+ attr_reader :goal
13
+
14
+ # @param theNames [Array<String>]
15
+ # @param aGoal [Goal]
16
+ def initialize(theNames, aGoal)
17
+ super()
18
+ @goal = aGoal
19
+ theNames.each { |nm| add_var(Core::Variable.new(nm)) }
20
+ end
21
+
22
+ # Attempt to achieve the goal given this environment
23
+ # @param aParent [Environment]
24
+ # @return [Fiber<Outcome>] A Fiber object that will generate the results.
25
+ def attain(aParent)
26
+ self.parent = aParent
27
+ goal.attain(self)
28
+ end
29
+ end # class
30
+ end # module
31
+ end # module
@@ -0,0 +1,43 @@
1
+ require_relative '../core/any_value'
2
+ require_relative '../core/cons_cell'
3
+ require_relative 'fresh_env'
4
+
5
+ module MiniKraken
6
+ module Glue
7
+ class RunStarExpression
8
+ attr_reader :env
9
+
10
+ # @param var_name [String]
11
+ # @param goal [Core::Goal]
12
+ def initialize(var_name, goal)
13
+ @env = FreshEnv.new([var_name], goal)
14
+ end
15
+
16
+ def var
17
+ env.vars.values.first
18
+ end
19
+
20
+ def run
21
+ result = nil
22
+ solver = env.goal.attain(env)
23
+ # require 'debug'
24
+ loop do
25
+ outcome = solver.resume
26
+ break if outcome.nil?
27
+ env.clear
28
+ if result # ... more than one result...
29
+ else
30
+ if outcome.successful?
31
+ env.propagate(outcome)
32
+ result = Core::ConsCell.new(var.quote(outcome))
33
+ else
34
+ result = Core::NullList
35
+ end
36
+ end
37
+ end
38
+
39
+ result
40
+ end
41
+ end # class
42
+ end # module
43
+ end # module
@@ -1,3 +1,3 @@
1
1
  module MiniKraken
2
- VERSION = "0.1.01"
2
+ VERSION = '0.1.02'.freeze
3
3
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+ require_relative '../../lib/mini_kraken/core/variable'
6
+
7
+ # Load the class under test
8
+ require_relative '../../lib/mini_kraken/core/association'
9
+
10
+ module MiniKraken
11
+ module Core
12
+ describe Association do
13
+ let(:pea) {KSymbol.new(:pea) }
14
+ subject { Association.new('q', pea) }
15
+
16
+ context 'Initialization:' do
17
+ it 'should be initialized with a name and a value' do
18
+ expect { Association.new('q', pea) }.not_to raise_error
19
+ end
20
+
21
+ it 'should be initialized with a variable and a value' do
22
+ expect { Association.new(Variable.new('p'), pea) }.not_to raise_error
23
+ end
24
+
25
+ it 'should know the variable name' do
26
+ expect(subject.var_name).to eq('q')
27
+ end
28
+
29
+ it 'should know the associated value' do
30
+ expect(subject.value).to eq(pea)
31
+ end
32
+ end # context
33
+
34
+ context 'Provided services:' do
35
+ end # context
36
+ end # describe
37
+ end # module
38
+ end # module
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/environment'
5
+ require_relative '../support/factory_methods'
6
+
7
+ # Load the class under test
8
+ require_relative '../../lib/mini_kraken/core/association_walker'
9
+
10
+
11
+ module MiniKraken
12
+ module Core
13
+ describe AssociationWalker do
14
+ include FactoryMethods
15
+
16
+ subject { AssociationWalker.new }
17
+
18
+ context 'Initialization:' do
19
+ it 'should be initialized without argument' do
20
+ expect { AssociationWalker.new }.not_to raise_error
21
+ end
22
+
23
+ it "shouldn't have any visitee at initialization" do
24
+ expect(subject.visitees.size).to eq(0)
25
+ end
26
+ end # context
27
+
28
+ context 'Provided services:' do
29
+ let(:pea) { KSymbol.new(:pea) }
30
+ let(:pod) { KSymbol.new(:pod) }
31
+ let(:var_q) { Variable.new('q') }
32
+ let(:ref_q) { VariableRef.new('q') }
33
+ let(:var_x) { Variable.new('x') }
34
+ let(:ref_x) { VariableRef.new('x') }
35
+ let(:env) { Environment.new }
36
+
37
+ it 'should return composite when it has only atomic term(s)' do
38
+ expr1 = cons(pea)
39
+ expect(subject.walk_value(expr1, env)).to eq(expr1)
40
+
41
+ expr2 = cons(pea, pod)
42
+ expect(subject.walk_value(expr2, env)).to eq(expr2)
43
+ end
44
+
45
+ it 'should return composite when it has ground composite term(s)' do
46
+ expr1 = cons(pea, cons(pod))
47
+ expect(subject.walk_value(expr1, env)).to eq(expr1)
48
+
49
+ expr2 = cons(pea, cons(pod, cons(pea, cons(pod))))
50
+ expect(subject.walk_value(expr2, env)).to eq(expr2)
51
+ end
52
+
53
+ it 'should return nil when there is one fresh variable' do
54
+ env.add_var(var_q)
55
+ expr1 = cons(pea, cons(pod, ref_q))
56
+ expect(subject.walk_value(expr1, env)).to be_nil
57
+ end
58
+
59
+ it 'should return composite when it has ground composite term(s)' do
60
+ env.add_var(var_q)
61
+ env.add_assoc(Association.new('q', pea))
62
+ expr1 = cons(pea, cons(pod, cons(ref_q)))
63
+ expect(subject.walk_value(expr1, env)).to eq(expr1)
64
+ end
65
+
66
+ it 'should return nil when no assocation exists' do
67
+ env.add_var(var_q)
68
+ env.add_assoc(Association.new('q', pea))
69
+ env.add_var(var_x)
70
+
71
+ expect(subject.find_ground(var_x.name, env)).to be_nil
72
+ end
73
+
74
+ it 'should find an atomic term directly associated' do
75
+ env.add_var(var_q)
76
+ env.add_assoc(Association.new('q', pea))
77
+
78
+ result = subject.find_ground(var_q.name, env)
79
+ expect(result).to eq(pea)
80
+ end
81
+
82
+ it 'should find an atomic term directly associated' do
83
+ env.add_var(var_q)
84
+ env.add_var(var_x)
85
+ env.add_assoc(Association.new('q', ref_x))
86
+ env.add_assoc(Association.new('x', pea))
87
+ expect(env['x']).not_to be_nil
88
+
89
+ result = subject.find_ground(var_q.name, env)
90
+ expect(result).to eq(pea)
91
+ end
92
+
93
+ it 'should cope with cyclic structures' do
94
+ env.add_var(var_q)
95
+ env.add_var(var_x)
96
+ env.add_assoc(Association.new('q', ref_x))
97
+ env.add_assoc(Association.new('x', pea))
98
+ env.add_assoc(Association.new('x', ref_q))
99
+
100
+ result = subject.find_ground(var_q.name, env)
101
+ expect(result).to eq(pea)
102
+
103
+ result = subject.find_ground(var_x.name, env)
104
+ expect(result).to eq(pea)
105
+ end
106
+
107
+ it 'should cope with a composite with atomic terms only' do
108
+ env.add_var(var_q)
109
+ expr = cons(pea, cons(pod, cons(pea)))
110
+ env.add_assoc(Association.new('q', expr))
111
+
112
+ result = subject.find_ground(var_q.name, env)
113
+ expect(result).to eq(expr)
114
+ end
115
+
116
+ it 'should cope with a composite with one fresh variable' do
117
+ env.add_var(var_q)
118
+ env.add_var(var_x)
119
+ expr = cons(pea, cons(pod, cons(ref_x)))
120
+ env.add_assoc(Association.new('q', expr))
121
+
122
+ result = subject.find_ground(var_q.name, env)
123
+ expect(result).to be_nil
124
+ end
125
+
126
+ it 'should cope with a composite with one ground variable' do
127
+ env.add_var(var_q)
128
+ env.add_var(var_x)
129
+ expr = cons(pea, cons(pod, cons(ref_x)))
130
+ env.add_assoc(Association.new('q', expr))
131
+ env.add_assoc(Association.new('x', pod))
132
+
133
+ result = subject.find_ground(var_q.name, env)
134
+ expect(result).to eq(expr)
135
+ end
136
+ =begin
137
+ =end
138
+ it 'should categorize a variable without association as free' do
139
+ env.add_var(var_q)
140
+ result = subject.determine_freshness(ref_q, env)
141
+ expect(result).to be_fresh
142
+ expect(result.associated).to be_nil
143
+ end
144
+
145
+ it 'should categorize a variable related to fresh variable as bound' do
146
+ env.add_var(var_q)
147
+ env.add_var(var_x)
148
+ env.add_assoc(Association.new('q', ref_x))
149
+
150
+ result = subject.determine_freshness(ref_q, env)
151
+ expect(result).to be_bound
152
+ expect(result.associated).to eq(ref_x)
153
+ end
154
+
155
+ it 'should categorize a variable even in presence of cycle(s)' do
156
+ env.add_var(var_q)
157
+ env.add_var(var_x)
158
+ env.add_assoc(Association.new('q', ref_x))
159
+ env.add_assoc(Association.new('x', ref_q))
160
+
161
+ result = subject.determine_freshness(ref_q, env)
162
+ expect(result).to be_bound
163
+ expect(result.associated).to eq(ref_x)
164
+ end
165
+
166
+ it 'should categorize an atomic term as ground term' do
167
+ result = subject.determine_freshness(pea, env)
168
+ expect(result).to be_ground
169
+ expect(result.associated).to eq(pea)
170
+ end
171
+
172
+ it 'should categorize a composite term as ground term' do
173
+ # Ground composite: a composite where all members are ground
174
+ composite = cons(pea, cons(pod))
175
+ result = subject.determine_freshness(composite, env)
176
+ expect(result).to be_ground
177
+ expect(result.associated).to eq(composite)
178
+ end
179
+
180
+ it 'should categorize a composite term as bound term' do
181
+ # Bound composite: a composite where at least one member is fresh
182
+ env.add_var(var_q)
183
+ composite = cons(pea, cons(ref_q))
184
+ result = subject.determine_freshness(composite, env)
185
+ expect(result).to be_bound
186
+ expect(result.associated).to eq(composite)
187
+ end
188
+ end # context
189
+ end # describe
190
+ end # module
191
+ end # module
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/mini_kraken/core/cons_cell'
8
+
9
+ module MiniKraken
10
+ module Core
11
+ describe ConsCell do
12
+ let(:pea) {KSymbol.new(:pea) }
13
+ let(:pod) {KSymbol.new(:pod) }
14
+ subject { ConsCell.new(pea, pod) }
15
+
16
+ context 'Initialization:' do
17
+ it 'could be initialized with one argument' do
18
+ expect { ConsCell.new(pea) }.not_to raise_error
19
+ end
20
+
21
+ it 'could be initialized with a second optional argument' do
22
+ expect { ConsCell.new(pea, pod) }.not_to raise_error
23
+ end
24
+
25
+ it 'should know its car child' do
26
+ expect(subject.car).to eq(pea)
27
+ end
28
+
29
+ it 'should know its cdr child' do
30
+ expect(subject.cdr).to eq(pod)
31
+ end
32
+
33
+ it 'should know its children' do
34
+ expect(subject.children).to eq([pea, pod])
35
+ end
36
+
37
+ it 'should know if it is empty (null)' do
38
+ expect(subject).not_to be_null
39
+ expect(ConsCell.new(nil, nil)).to be_null
40
+ expect(NullList).to be_null
41
+ end
42
+ end # context
43
+
44
+ context 'Provided services:' do
45
+ it 'should compare to itself' do
46
+ expect(subject.eql?(subject)).to be_truthy
47
+ expect(subject == subject).to be_truthy
48
+ end
49
+
50
+ it 'should compare to another instance' do
51
+ same = ConsCell.new(pea, pod)
52
+ expect(subject.eql?(same)).to be_truthy
53
+
54
+ different = ConsCell.new(pod, pea)
55
+ expect(subject.eql?(different)).to be_falsey
56
+
57
+ different = ConsCell.new(pea)
58
+ expect(subject.eql?(different)).to be_falsey
59
+ end
60
+ end # context
61
+ end # describe
62
+ end # module
63
+ end # module
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/environment'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/mini_kraken/core/duck_fiber'
8
+
9
+ module MiniKraken
10
+ module Core
11
+ describe DuckFiber do
12
+ subject { DuckFiber.new(:failure) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should be initialized with a symbol and an optional block' do
16
+ expect { DuckFiber.new(:failure) }.not_to raise_error
17
+
18
+ expect { DuckFiber.new(:custom) { Outcome.new(:"#s") } }.not_to raise_error
19
+ end
20
+
21
+ it 'should know its outcome' do
22
+ expect(subject.outcome).to eq(Failure)
23
+ end
24
+ end # context
25
+
26
+ context 'Provided services:' do
27
+ let(:parent) { Environment.new }
28
+
29
+ it 'should behave like a Fiber yielding a failure' do
30
+ failing = DuckFiber.new(:failure)
31
+ outcome = nil
32
+ expect { outcome = failing.resume }.not_to raise_error
33
+ expect(outcome).to eq(Failure)
34
+
35
+ # Only one result should be yielded
36
+ expect(failing.resume).to be_nil
37
+ end
38
+
39
+ it 'should behave like a Fiber yielding a basic success' do
40
+ succeeding = DuckFiber.new(:success)
41
+ outcome = nil
42
+ expect { outcome = succeeding.resume }.not_to raise_error
43
+ expect(outcome).to eq(BasicSuccess)
44
+
45
+ # Only one result should be yielded
46
+ expect(succeeding.resume).to be_nil
47
+ end
48
+
49
+ it 'should behave like a Fiber yielding a custom outcome' do
50
+
51
+ tailored = DuckFiber.new(:custom) { Outcome.new(:"#s", parent) }
52
+ outcome = nil
53
+ expect { outcome = tailored.resume }.not_to raise_error
54
+ expect(outcome).to eq(Outcome.new(:"#s", parent))
55
+
56
+ # Only one result should be yielded
57
+ expect(tailored.resume).to be_nil
58
+ end
59
+ end # context
60
+ end # describe
61
+ end # module
62
+ end # module
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+ require_relative '../../lib/mini_kraken/core/variable'
6
+ require_relative '../../lib/mini_kraken/core/variable_ref'
7
+ require_relative '../../lib/mini_kraken/core/association'
8
+ require_relative '../../lib/mini_kraken/core/outcome'
9
+
10
+ # Load the class under test
11
+ require_relative '../../lib/mini_kraken/core/environment'
12
+
13
+ module MiniKraken
14
+ module Core
15
+ describe Environment do
16
+ subject { Environment.new }
17
+
18
+ context 'Initialization:' do
19
+ it 'should be initialized with an optional parent' do
20
+ expect { Environment.new }.not_to raise_error
21
+ parent = Environment.new
22
+ expect { Environment.new(parent) }.not_to raise_error
23
+ end
24
+
25
+ it "shouldn't have variable by default" do
26
+ expect(subject.vars).to be_empty
27
+ end
28
+
29
+ it "shouldn't have associations by default" do
30
+ expect(subject.associations).to be_empty
31
+ end
32
+
33
+ it 'shold know its parent (if any)' do
34
+ # Case: no parent
35
+ expect(subject.parent).to be_nil
36
+
37
+ # Case: there is a parent
38
+ child = Environment.new(subject)
39
+ expect(child.parent).to eq(subject)
40
+ end
41
+
42
+ end # context
43
+
44
+ context 'Provided services:' do
45
+ let(:var_a) { Variable.new('a') }
46
+ let(:var_b) { Variable.new('b') }
47
+ let(:var_c) { Variable.new('c') }
48
+ let(:var_c_bis) { Variable.new('c') }
49
+ let(:pea) { KSymbol.new(:pea) }
50
+ let(:pod) { KSymbol.new(:pod) }
51
+ let(:pad) { KSymbol.new(:pad) }
52
+
53
+ it 'should accept the addition of a variable' do
54
+ subject.add_var(var_a)
55
+ expect(subject.vars).not_to be_empty
56
+ expect(subject.vars['a']).to eq(var_a)
57
+ end
58
+
59
+ it 'should accept the addition of multiple variables' do
60
+ subject.add_var(var_a)
61
+ expect(subject.vars).not_to be_empty
62
+ subject.add_var(var_b)
63
+ expect(subject.vars['a']).to eq(var_a)
64
+ expect(subject.vars['b']).to eq(var_b)
65
+ end
66
+
67
+ it 'should accept the addition of an association' do
68
+ subject.add_var(var_a)
69
+ assoc = Association.new('a', pea)
70
+ subject.add_assoc(assoc)
71
+ expect(subject.associations.size).to eq(1)
72
+ expect(subject.associations['a']).to eq([assoc])
73
+ end
74
+
75
+ it 'should tell that a newborn variable is fresh' do
76
+ subject.add_var(var_a)
77
+
78
+ # By default, a variable is fresh...
79
+ expect(subject.fresh?(var_a)).to be_truthy
80
+ end
81
+
82
+ it "should tell variable associated with a literal value isn't fresh" do
83
+ subject.add_var(var_a)
84
+
85
+ # Let's associate an atomic term...
86
+ assoc = Association.new('a', pea)
87
+ subject.add_assoc(assoc)
88
+
89
+ expect(subject.fresh?(var_a)).to be_falsey
90
+ end
91
+
92
+ it "should cope with a variable associated with another variable" do
93
+ subject.add_var(var_a)
94
+ subject.add_var(var_b)
95
+
96
+ # Let's associate a with (fresh) b
97
+ assoc = Association.new(var_a, var_b)
98
+ subject.add_assoc(assoc)
99
+
100
+ expect(subject.fresh?(var_a)).to be_truthy
101
+
102
+ # Now associate b with something ground...
103
+ assoc_b = Association.new(var_b, pea)
104
+ subject.add_assoc(assoc_b)
105
+
106
+ # b is no more fresh, so is ... a
107
+ expect(subject.fresh?(var_b)).to be_falsey
108
+ expect(subject.fresh?(var_a)).to be_falsey
109
+ end
110
+
111
+ it 'should remove all associations' do
112
+ subject.add_var(var_a)
113
+ assoc = Association.new(var_a, pea)
114
+ subject.add_assoc(assoc)
115
+
116
+ subject.add_var(var_b)
117
+ assoc = Association.new(var_b, pod)
118
+ subject.add_assoc(assoc)
119
+
120
+ subject.clear
121
+ expect(subject.fresh?(var_a)).to be_truthy
122
+ expect(subject.fresh?(var_a)).to be_truthy
123
+ end
124
+
125
+ it 'should propagate associations up in the environment hierarchy' do
126
+ parent = Environment.new
127
+ parent.add_var(var_a)
128
+ # parent.add_var(var_c)
129
+ instance = Environment.new(parent)
130
+ instance.add_var(var_b)
131
+ # instance.add_var(var_c_bis) # Should shadow parent's c variable
132
+
133
+ outcome = Outcome.new(:"#s", instance)
134
+ outcome.add_assoc(Association.new(var_a, pea))
135
+ outcome.add_assoc(Association.new(var_b, pod))
136
+ # outcome.add_assoc(Association.new(var_c_bis, pad))
137
+ expect(outcome.associations.size).to eq(2)
138
+
139
+ # Propagate: outcome -> .. -> instance
140
+ instance.propagate(outcome)
141
+ expect(outcome.associations.size).to eq(1)
142
+ expect(instance.associations[var_b.name]).not_to be_nil
143
+ expect(parent.associations[var_a.name]).to be_nil
144
+
145
+ # Propagate: outcome -> .. -> parent
146
+ parent.propagate(outcome)
147
+ expect(outcome.associations).to be_empty
148
+ expect(parent.associations[var_b.name]).to be_nil
149
+ expect(parent.associations[var_a.name]).not_to be_nil
150
+ end
151
+ end # context
152
+ end # describe
153
+ end # module
154
+ end # module