ellington 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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