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.
- data/Gemfile +14 -2
- data/Gemfile.lock +40 -0
- data/README.md +66 -17
- data/lib/ellington.rb +6 -4
- data/lib/ellington/attendant.rb +21 -0
- data/lib/ellington/conductor.rb +59 -0
- data/lib/ellington/connection.rb +11 -0
- data/lib/ellington/connection_list.rb +29 -0
- data/lib/ellington/errros.rb +22 -0
- data/lib/ellington/has_targets.rb +29 -0
- data/lib/ellington/line.rb +95 -0
- data/lib/ellington/line_info.rb +34 -0
- data/lib/ellington/line_list.rb +24 -0
- data/lib/ellington/logger.rb +11 -0
- data/lib/ellington/passenger.rb +67 -0
- data/lib/ellington/route.rb +114 -0
- data/lib/ellington/route_info.rb +25 -0
- data/lib/ellington/station.rb +88 -0
- data/lib/ellington/station_info.rb +11 -0
- data/lib/ellington/station_list.rb +24 -0
- data/lib/ellington/target.rb +25 -0
- data/lib/ellington/ticket.rb +22 -0
- data/lib/ellington/transition_info.rb +4 -0
- data/lib/ellington/unique_type_array.rb +39 -0
- data/lib/ellington/version.rb +1 -1
- data/test/attendant_test.rb +42 -0
- data/test/conductor_test.rb +32 -0
- data/test/example.rb +212 -0
- data/test/line_test.rb +95 -0
- data/test/passenger_test.rb +73 -0
- data/test/route_test.rb +51 -0
- data/test/station_test.rb +115 -0
- data/test/target_test.rb +54 -0
- data/test/test_helper.rb +6 -0
- data/test/ticket_test.rb +39 -0
- metadata +92 -14
- data/.gitignore +0 -17
- data/ellington.gemspec +0 -19
@@ -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
|
data/lib/ellington/version.rb
CHANGED
@@ -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
|