ellington 0.0.1 → 0.0.2

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.
@@ -0,0 +1,4 @@
1
+ module Ellington
2
+ class TransitionInfo < Struct.new(:passenger, :old_state, :new_state)
3
+ end
4
+ end
@@ -0,0 +1,39 @@
1
+ require "delegate"
2
+
3
+ module Ellington
4
+ class UniqueTypeArray < SimpleDelegator
5
+
6
+ def initialize
7
+ @inner_list = []
8
+ super inner_list
9
+ end
10
+
11
+ def push(value)
12
+ check value
13
+ inner_list.push value
14
+ end
15
+
16
+ def <<(value)
17
+ check value
18
+ inner_list << value
19
+ end
20
+
21
+ def contains_a?(klass)
22
+ each do |entry|
23
+ return true if entry.class == klass
24
+ end
25
+ false
26
+ end
27
+
28
+ protected
29
+
30
+ attr_reader :inner_list
31
+
32
+ def check(value)
33
+ if contains_a?(value.class)
34
+ raise Ellington::ListAlreadyContainsType.new("List already contains a #{value.class.name} type!")
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module Ellington
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,42 @@
1
+ require_relative "test_helper"
2
+
3
+ class AttendantTest < MicroTest::Test
4
+
5
+ before do
6
+ @route = BasicMath.new
7
+ @line = @route.lines.first
8
+ @station = @line.stations.first
9
+ @passenger = Ellington::Passenger.new(NumberWithHistory.new(0), @route)
10
+ @passenger.current_state = @route.initial_state
11
+ @passenger.lock
12
+ @attendant = Ellington::Attendant.new(@station)
13
+ @passenger.add_observer @attendant
14
+ end
15
+
16
+ test "passenger transition is captured" do
17
+ @passenger.transition_to @station.passed
18
+ assert @attendant.passenger_transitions.length == 1
19
+ end
20
+
21
+ test "approves of single passenger transition" do
22
+ @passenger.transition_to @station.passed
23
+ assert @attendant.approve?
24
+ end
25
+
26
+ test "multiple passenger transitions are captured" do
27
+ @passenger.transition_to @station.errored
28
+ @passenger.transition_to @station.passed
29
+ assert @attendant.passenger_transitions.length == 2
30
+ end
31
+
32
+ test "disapproves of multiple passenger transitions" do
33
+ @passenger.transition_to @station.errored
34
+ @passenger.transition_to @station.passed
35
+ assert !@attendant.approve?
36
+ end
37
+
38
+ test "a transition must be made in order to be approved" do
39
+ assert !@attendant.approve?
40
+ end
41
+
42
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "test_helper"
2
+
3
+ class ConductorTest < MicroTest::Test
4
+
5
+ #before do
6
+ # route = Ellington::Route.new("Example Route", StateJacket::Catalog.new)
7
+ # route.add Ellington::Line.new("A Line")
8
+ # route["A Line"] << ConductorTest::Station.new
9
+ # @conductor = Ellington::Conductor.new(route)
10
+ #end
11
+
12
+ #test "verify is abstract" do
13
+ # error = nil
14
+ # begin
15
+ # @conductor.verify nil
16
+ # rescue Ellington::NotImplementedError => e
17
+ # error = e
18
+ # end
19
+ # assert !error.nil?
20
+ #end
21
+
22
+ #test "gather_passengers is abstract" do
23
+ # error = nil
24
+ # begin
25
+ # @conductor.gather_passengers
26
+ # rescue Ellington::NotImplementedError => e
27
+ # error = e
28
+ # end
29
+ # assert !error.nil?
30
+ #end
31
+
32
+ end
data/test/example.rb ADDED
@@ -0,0 +1,212 @@
1
+ require "logger"
2
+ require_relative "../lib/ellington"
3
+
4
+ require "yell"
5
+ Ellington.logger = Yell.new do |logger|
6
+ logger.adapter STDOUT, :level => [:info], :format => "%m"
7
+ end
8
+ #Ellington.logger = Logger.new($stdout)
9
+
10
+ class NumberWithHistory
11
+ attr_reader :original_value, :current_value, :history
12
+ def initialize(value)
13
+ @original_value = value
14
+ @current_value = value
15
+ @history = []
16
+ end
17
+
18
+ def calc(operand, other)
19
+ value = current_value
20
+ @current_value = current_value.send(operand, other)
21
+ history.push(:before => value, :after => current_value)
22
+ current_value
23
+ end
24
+ end
25
+
26
+ # stations -----------------------------------------------------------------
27
+ class Add10 < Ellington::Station
28
+ def engage(passenger, options)
29
+ raise if rand(100) == 0
30
+ if rand(100) > 5
31
+ passenger.calc :+, 10
32
+ pass passenger
33
+ else
34
+ fail passenger
35
+ end
36
+ rescue
37
+ error passenger
38
+ end
39
+ end
40
+
41
+ class Add100 < Ellington::Station
42
+ def engage(passenger, options)
43
+ raise if rand(100) == 0
44
+ if rand(100) > 5
45
+ passenger.calc :+, 100
46
+ pass passenger
47
+ else
48
+ fail passenger
49
+ end
50
+ rescue
51
+ error passenger
52
+ end
53
+ end
54
+
55
+ class Add1000 < Ellington::Station
56
+ def engage(passenger, options)
57
+ raise if rand(100) == 0
58
+ if rand(100) > 5
59
+ passenger.calc :+, 1000
60
+ pass passenger
61
+ else
62
+ fail passenger
63
+ end
64
+ rescue
65
+ error passenger
66
+ end
67
+ end
68
+
69
+ class MultiplyBy10 < Ellington::Station
70
+ def engage(passenger, options)
71
+ raise if rand(100) == 0
72
+ if rand(100) > 5
73
+ passenger.calc :*, 10
74
+ pass passenger
75
+ else
76
+ fail passenger
77
+ end
78
+ rescue
79
+ error passenger
80
+ end
81
+ end
82
+
83
+ class MultiplyBy100 < Ellington::Station
84
+ def engage(passenger, options)
85
+ raise if rand(100) == 0
86
+ if rand(100) > 5
87
+ passenger.calc :*, 100
88
+ pass passenger
89
+ else
90
+ fail passenger
91
+ end
92
+ rescue
93
+ error passenger
94
+ end
95
+ end
96
+
97
+ class MultiplyBy1000 < Ellington::Station
98
+ def engage(passenger, options)
99
+ raise if rand(100) == 0
100
+ if rand(100) > 5
101
+ passenger.calc :*, 1000
102
+ pass passenger
103
+ else
104
+ fail passenger
105
+ end
106
+ rescue
107
+ error passenger
108
+ end
109
+ end
110
+
111
+ class DivideBy10 < Ellington::Station
112
+ def engage(passenger, options)
113
+ raise if rand(100) == 0
114
+ if rand(100) > 5
115
+ passenger.calc :/, 10.0
116
+ pass passenger
117
+ else
118
+ fail passenger
119
+ end
120
+ rescue
121
+ error passenger
122
+ end
123
+ end
124
+
125
+ class DivideBy100 < Ellington::Station
126
+ def engage(passenger, options)
127
+ raise if rand(100) == 0
128
+ if rand(100) > 5
129
+ passenger.calc :/, 100.0
130
+ pass passenger
131
+ else
132
+ fail passenger
133
+ end
134
+ rescue
135
+ error passenger
136
+ end
137
+ end
138
+
139
+ class DivideBy1000 < Ellington::Station
140
+ def engage(passenger, options)
141
+ raise if rand(100) == 0
142
+ if rand(100) > 5
143
+ passenger.calc :/, 1000.0
144
+ pass passenger
145
+ else
146
+ fail passenger
147
+ end
148
+ rescue
149
+ error passenger
150
+ end
151
+ end
152
+
153
+ # lines --------------------------------------------------------------------
154
+ class Addition < Ellington::Line
155
+ stations << Add10.new
156
+ stations << Add100.new
157
+ stations << Add1000.new
158
+ goal stations.last.passed
159
+ end
160
+
161
+ class Multiplication < Ellington::Line
162
+ stations << MultiplyBy10.new
163
+ stations << MultiplyBy100.new
164
+ stations << MultiplyBy1000.new
165
+ goal stations.last.passed
166
+ end
167
+
168
+ class Division < Ellington::Line
169
+ stations << DivideBy10.new
170
+ stations << DivideBy100.new
171
+ stations << DivideBy1000.new
172
+ goal stations.last.passed
173
+ end
174
+
175
+ # route -------------------------------------------------------------------
176
+ class BasicMath < Ellington::Route
177
+ addition = Addition.new
178
+ multiplication = Multiplication.new
179
+ division = Division.new
180
+
181
+ lines << addition
182
+ lines << division
183
+ lines << multiplication
184
+
185
+ goal division.passed, multiplication.passed
186
+
187
+ connect_to division, :if => addition.passed
188
+ connect_to multiplication, :if => addition.failed
189
+
190
+ log_passenger_attrs :original_value, :current_value
191
+ end
192
+
193
+ # conductor ---------------------------------------------------------------
194
+ class NumberConductor < Ellington::Conductor
195
+
196
+ def gather_passengers
197
+ (0..999).to_a.sample(10).map do |num|
198
+ num = NumberWithHistory.new(num)
199
+ passenger = Ellington::Passenger.new(num, route)
200
+ passenger.current_state = route.initial_state
201
+ passenger.lock
202
+ passenger
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ if ENV["START"]
209
+ route = BasicMath.new
210
+ conductor = NumberConductor.new(route)
211
+ conductor.start
212
+ end
data/test/line_test.rb ADDED
@@ -0,0 +1,95 @@
1
+ require_relative "test_helper"
2
+
3
+ class LineTest < MicroTest::Test
4
+
5
+ before do
6
+ @route = BasicMath.new
7
+ @line = @route.lines.first
8
+ end
9
+
10
+ test "stations on class" do
11
+ assert @line.stations.length == 3
12
+ assert @line.stations[0].is_a?(Add10)
13
+ assert @line.stations[1].is_a?(Add100)
14
+ assert @line.stations[2].is_a?(Add1000)
15
+ end
16
+
17
+ test "lines on instance" do
18
+ assert @line.stations.length == 3
19
+ assert @line.stations[0].is_a?(Add10)
20
+ assert @line.stations[1].is_a?(Add100)
21
+ assert @line.stations[2].is_a?(Add1000)
22
+ end
23
+
24
+ test "type of stations must be unique" do
25
+ begin
26
+ @line.stations << Add10.new
27
+ rescue Ellington::ListAlreadyContainsType => e
28
+ end
29
+ assert !e.nil?
30
+ end
31
+
32
+ test "stations are assigned line" do
33
+ @line.stations.each do |station|
34
+ assert station.line == @line.class
35
+ end
36
+ end
37
+
38
+ test "name" do
39
+ line = @route.lines.first
40
+ assert line.name == "Addition::BasicMath"
41
+ end
42
+
43
+ test "formula" do
44
+ line = @route.lines.first
45
+ assert line.formula.steps[0].last == line.stations[0]
46
+ assert line.formula.steps[1].last == line.stations[1]
47
+ assert line.formula.steps[2].last == line.stations[2]
48
+ end
49
+
50
+ test "station1 'PASS' can transition to all station2 states" do
51
+ line = @route.lines.first
52
+ pass = line.stations[0].state_name(:pass)
53
+ transitions = line.states[pass]
54
+ assert transitions.include?(line.stations[1].state_name(:pass))
55
+ assert transitions.include?(line.stations[1].state_name(:fail))
56
+ assert transitions.include?(line.stations[1].state_name(:error))
57
+ end
58
+
59
+ test "station2 'PASS' can transition to all station3 states" do
60
+ line = @route.lines.first
61
+ pass = line.stations[1].state_name(:pass)
62
+ transitions = line.states[pass]
63
+ assert transitions.include?(line.stations[2].state_name(:pass))
64
+ assert transitions.include?(line.stations[2].state_name(:fail))
65
+ assert transitions.include?(line.stations[2].state_name(:error))
66
+ end
67
+
68
+ test "station3 'PASS' is terminal" do
69
+ line = @route.lines.first
70
+ pass = line.stations[2].state_name(:pass)
71
+ transitions = line.states[pass]
72
+ assert transitions.nil?
73
+ end
74
+
75
+ test "station3 'FAIL' is terminal" do
76
+ line = @route.lines.first
77
+ pass = line.stations[2].state_name(:fail)
78
+ transitions = line.states[pass]
79
+ assert transitions.nil?
80
+ end
81
+
82
+ test "pass_target" do
83
+ line = @route.lines.first
84
+ assert line.goal == [line.stations[2].passed]
85
+ end
86
+
87
+ test "fail_target" do
88
+ line = @route.lines.first
89
+ expected = (line.states.keys - line.goal).delete_if do |state|
90
+ state.to_s =~ /\AERROR/
91
+ end
92
+ assert line.fail_target == expected
93
+ end
94
+
95
+ end
@@ -0,0 +1,73 @@
1
+ require_relative "test_helper"
2
+
3
+ class PassengerTest < MicroTest::Test
4
+
5
+ before do
6
+ @route = BasicMath.new
7
+ @number = NumberWithHistory.new(0)
8
+ @passenger = Ellington::Passenger.new(@number, @route)
9
+ end
10
+
11
+ test "lock" do
12
+ @passenger.lock
13
+ assert @passenger.locked?
14
+ end
15
+
16
+ test "unlock" do
17
+ @passenger.lock
18
+ @passenger.unlock
19
+ assert !@passenger.locked?
20
+ end
21
+
22
+ test "transition_to fails when unlocked" do
23
+ error = nil
24
+ begin
25
+ @passenger.current_state = @route.initial_state
26
+ @passenger.transition_to @route.lines.first.states.keys.first
27
+ rescue Ellington::InvalidStateTransition => e
28
+ error = e
29
+ end
30
+ assert !error.nil?
31
+ assert error.message == "Cannot transition an unlocked Ellington::Passenger's state"
32
+ end
33
+
34
+ test "transition_to valid state" do
35
+ @passenger.lock
36
+ @passenger.current_state = @route.initial_state
37
+ @passenger.transition_to @route.lines.first.states.keys.first
38
+ @passenger.unlock
39
+ assert @passenger.current_state == @route.lines.first.states.keys.first
40
+ end
41
+
42
+ test "transition_to invalid state" do
43
+ error = nil
44
+ begin
45
+ @passenger.lock
46
+ @passenger.current_state = :error
47
+ @passenger.transition_to :happy
48
+ rescue Ellington::InvalidStateTransition => e
49
+ error = e
50
+ end
51
+ assert !error.nil?
52
+ assert error.message == "Cannot transition Ellington::Passenger from:error to:happy"
53
+ end
54
+
55
+ test "observers are notified on transition" do
56
+ watcher = MicroMock.make.new
57
+ watcher.attrs(:info)
58
+ watcher.def(:update) do |info|
59
+ self.info = info
60
+ end
61
+
62
+ @passenger.add_observer(watcher)
63
+ @passenger.lock
64
+ @passenger.current_state = @route.initial_state
65
+ @passenger.transition_to @route.lines.first.states.keys.first
66
+ @passenger.unlock
67
+
68
+ assert watcher.info.passenger == @passenger
69
+ assert watcher.info.old_state == "PRE BasicMath"
70
+ assert watcher.info.new_state == "PASS Add10::Addition"
71
+ end
72
+
73
+ end