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