alter-ego 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ class AssertionFailureError < Exception
2
+ end
3
+
4
+ module Assertions
5
+
6
+ # Assert that no +values+ are nil or false. Returns the last value.
7
+ def assert(*values, &block)
8
+ iterate_and_return_last(values, block) do |v|
9
+ raise_assertion_error unless v
10
+ end
11
+ end
12
+
13
+ # The opposite of #assert.
14
+ def deny(*values)
15
+ assert(*values.map{ |v| !v})
16
+ assert(yield(*values)) if block_given?
17
+ values.last
18
+ end
19
+
20
+ # Assert that no +values+ are nil. Returns the last value.
21
+ def assert_exists(*values, &block)
22
+ iterate_and_return_last(values, block) { |value| deny(value.nil?) }
23
+ end
24
+
25
+ # Assert that +values+ are collections that contain at least one element.
26
+ # Returns the last value.
27
+ def assert_one_or_more(*values, &block)
28
+ iterate_and_return_last(values, block) do |value|
29
+ assert_exists(value)
30
+ deny(value.kind_of?(String))
31
+ deny(value.empty?)
32
+ end
33
+ end
34
+
35
+ def assert_keys(hash, *keys)
36
+ assert_exists(hash)
37
+ assert(hash.respond_to?(:[]))
38
+ values = keys.inject([]) { |vals, k| vals << assert_exists(hash[k]) }
39
+ assert(yield(*values)) if block_given?
40
+ hash
41
+ end
42
+
43
+ private
44
+
45
+ def iterate_and_return_last(values, block = nil)
46
+ values.each { |v| yield(v) }
47
+ if block
48
+ raise_assertion_error unless block.call(*values)
49
+ end
50
+ values.last
51
+ end
52
+
53
+ def raise_assertion_error
54
+ error = AssertionFailureError.new
55
+ backtrace = caller
56
+ trimmed_backtrace = []
57
+ trimmed_backtrace.unshift(backtrace.pop) until
58
+ backtrace.last.include?(__FILE__)
59
+ error.set_backtrace(trimmed_backtrace)
60
+ raise error
61
+ end
62
+
63
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/alter_ego.rb'}"
9
+ puts "Loading AlterEgo gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,1051 @@
1
+ require 'ostruct'
2
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
3
+
4
+ # let's define a traffic light class with three states: proceed, caution, and
5
+ # stop. We'll leave the DSL for later, and use old-school class definitions to
6
+ # start out.
7
+
8
+ class TrafficLightWithClassicStates
9
+ include AlterEgo
10
+
11
+ class ProceedState < State
12
+ end
13
+
14
+ class CautionState < State
15
+ end
16
+
17
+ class StopState < State
18
+ end
19
+
20
+ add_state(ProceedState)
21
+ add_state(CautionState)
22
+ add_state(StopState)
23
+
24
+ end
25
+
26
+ describe TrafficLightWithClassicStates do
27
+ before :each do
28
+ @it = TrafficLightWithClassicStates
29
+ end
30
+
31
+ it "should have the specified states" do
32
+ @it.states.values.should include(TrafficLightWithClassicStates::ProceedState)
33
+ @it.states.values.should include(TrafficLightWithClassicStates::CautionState)
34
+ @it.states.values.should include(TrafficLightWithClassicStates::StopState)
35
+ end
36
+ end
37
+
38
+ # Before we go any further, we'll define some identifiers for our states. This
39
+ # will make them easier to work with.
40
+
41
+ class TrafficLightWithIdentifiers
42
+ include AlterEgo
43
+
44
+ class ProceedState < State
45
+ def self.identifier; :proceed; end
46
+ end
47
+
48
+ class CautionState < State
49
+ def self.identifier; :caution; end
50
+ end
51
+
52
+ class StopState < State
53
+ def self.identifier; :stop; end
54
+ end
55
+
56
+ add_state(ProceedState)
57
+ add_state(CautionState)
58
+ add_state(StopState)
59
+
60
+ def initialize(starting_state = :proceed)
61
+ self.state=(starting_state)
62
+ end
63
+
64
+ def cycle
65
+ case current_state.identifier
66
+ when :proceed then transition_to(:caution)
67
+ when :caution then transition_to(:stop)
68
+ when :stop then transition_to(:proceed)
69
+ else raise "Should never get here"
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "a green light", :shared => true do
75
+ it "should be in 'proceed' state" do
76
+ @it.current_state.should == :proceed
77
+ end
78
+
79
+ it "should change to the caution (yellow) state on cycle" do
80
+ @it.cycle
81
+ @it.current_state.should == :caution
82
+ end
83
+ end
84
+
85
+ describe "a yellow light", :shared => true do
86
+ it "should be in 'caution' state" do
87
+ @it.current_state.should == :caution
88
+ end
89
+
90
+ it "should change to stop (red) on cycle" do
91
+ @it.cycle
92
+ @it.current_state.should == :stop
93
+ end
94
+ end
95
+
96
+ describe "a red light", :shared => true do
97
+ it "should be in 'stop' state" do
98
+ @it.current_state.should == :stop
99
+ end
100
+
101
+ it "should change to proceed (green) on cycle" do
102
+ @it.cycle
103
+ @it.current_state.should == :proceed
104
+ end
105
+ end
106
+
107
+ describe TrafficLightWithIdentifiers, "by default" do
108
+ before :each do
109
+ @it = TrafficLightWithIdentifiers.new
110
+ end
111
+
112
+ it_should_behave_like "a green light"
113
+ end
114
+
115
+ describe TrafficLightWithIdentifiers, "when yellow" do
116
+ before :each do
117
+ @it = TrafficLightWithIdentifiers.new(:caution)
118
+ end
119
+
120
+ it_should_behave_like "a yellow light"
121
+ end
122
+
123
+ describe TrafficLightWithIdentifiers, "when red" do
124
+ before :each do
125
+ @it = TrafficLightWithIdentifiers.new(:stop)
126
+ end
127
+
128
+ it_should_behave_like "a red light"
129
+ end
130
+
131
+ # Being able to go from one state to another isn't that big a deal. Let's add
132
+ # some state-specific behaviour.
133
+
134
+ class TrafficLightWithColors
135
+ include AlterEgo
136
+
137
+ class ProceedState < State
138
+ def self.identifier
139
+ :proceed
140
+ end
141
+ def color(traffic_light)
142
+ "green"
143
+ end
144
+ end
145
+
146
+ class CautionState < State
147
+ def self.identifier
148
+ :caution
149
+ end
150
+ def color(traffic_light)
151
+ "yellow"
152
+ end
153
+ end
154
+
155
+ class StopState < State
156
+ def self.identifier
157
+ :stop
158
+ end
159
+ def color(traffic_light)
160
+ "red"
161
+ end
162
+ end
163
+
164
+ add_state(ProceedState)
165
+ add_state(CautionState)
166
+ add_state(StopState)
167
+
168
+ def initialize(starting_state = :proceed)
169
+ self.state=(starting_state)
170
+ end
171
+
172
+ def cycle
173
+ case current_state.identifier
174
+ when :proceed then transition_to(:caution)
175
+ when :caution then transition_to(:stop)
176
+ when :stop then transition_to(:proceed)
177
+ else raise "Should never get here"
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "a green light with color", :shared => true do
183
+ it_should_behave_like "a green light"
184
+ it "should have color green" do
185
+ @it.color.should == "green"
186
+ end
187
+ end
188
+
189
+ describe "a yellow light with color", :shared => true do
190
+ it_should_behave_like "a yellow light"
191
+ it "should have color yellow" do
192
+ @it.color.should == "yellow"
193
+ end
194
+ end
195
+
196
+ describe "a red light with color", :shared => true do
197
+ it_should_behave_like "a red light"
198
+ it "should have color red" do
199
+ @it.color.should == "red"
200
+ end
201
+ end
202
+
203
+ describe TrafficLightWithColors, "when green" do
204
+ before :each do
205
+ @it = TrafficLightWithColors.new(:proceed)
206
+ end
207
+ it_should_behave_like "a green light with color"
208
+ end
209
+
210
+ describe TrafficLightWithColors, "when yellow" do
211
+ before :each do
212
+ @it = TrafficLightWithColors.new(:caution)
213
+ end
214
+ it_should_behave_like "a yellow light with color"
215
+ end
216
+
217
+ describe TrafficLightWithColors, "when red" do
218
+ before :each do
219
+ @it = TrafficLightWithColors.new(:stop)
220
+ end
221
+ it_should_behave_like "a red light with color"
222
+ end
223
+
224
+ # This is all very verbose. Now that we have a feel for the object model, let's
225
+ # introduce the DSL syntax. Notice that the identifier becomes an argument to
226
+ # the 'state' declaration, and the #color methods become "handlers". Also note
227
+ # there is no longer any need for an explicit #add_state call.
228
+
229
+ class TrafficLightDescribedByDsl
230
+ include AlterEgo
231
+
232
+ state :proceed do
233
+ handle :color do
234
+ "green"
235
+ end
236
+ end
237
+
238
+ state :caution do
239
+ handle :color do
240
+ "yellow"
241
+ end
242
+ end
243
+
244
+ state :stop do
245
+ handle :color do
246
+ "red"
247
+ end
248
+ end
249
+
250
+ def initialize(starting_state = :proceed)
251
+ self.state=(starting_state)
252
+ end
253
+
254
+ def cycle
255
+ case current_state.identifier
256
+ when :proceed then transition_to(:caution)
257
+ when :caution then transition_to(:stop)
258
+ when :stop then transition_to(:proceed)
259
+ else raise "Should never get here"
260
+ end
261
+ end
262
+ end
263
+
264
+ describe TrafficLightDescribedByDsl, "when green" do
265
+ before :each do
266
+ @it = TrafficLightDescribedByDsl.new(:proceed)
267
+ end
268
+
269
+ it_should_behave_like "a green light with color"
270
+ end
271
+
272
+ describe TrafficLightDescribedByDsl, "when yellow" do
273
+ before :each do
274
+ @it = TrafficLightDescribedByDsl.new(:caution)
275
+ end
276
+
277
+ it_should_behave_like "a yellow light with color"
278
+ end
279
+
280
+ describe TrafficLightDescribedByDsl, "when red" do
281
+ before :each do
282
+ @it = TrafficLightDescribedByDsl.new(:stop)
283
+ end
284
+
285
+ it_should_behave_like "a red light with color"
286
+ end
287
+
288
+ # Let's redefine #cycle to be just another handler. Note that when defined
289
+ # with the 'handler' syntax, handler blocks are executed in the context of the
290
+ # context object, that is, the object which has a state.
291
+
292
+ class TrafficLightWithCycleHandler
293
+ include AlterEgo
294
+
295
+ state :proceed do
296
+ handle :color do
297
+ "green"
298
+ end
299
+ handle :cycle do
300
+ transition_to(:caution)
301
+ end
302
+ end
303
+
304
+ state :caution do
305
+ handle :color do
306
+ "yellow"
307
+ end
308
+ handle :cycle do
309
+ transition_to(:stop)
310
+ end
311
+ end
312
+
313
+ state :stop do
314
+ handle :color do
315
+ "red"
316
+ end
317
+ handle :cycle do
318
+ transition_to(:proceed)
319
+ end
320
+ end
321
+
322
+ def initialize(starting_state = :proceed)
323
+ self.state=(starting_state)
324
+ end
325
+ end
326
+
327
+ describe TrafficLightWithCycleHandler, "when green" do
328
+ before :each do
329
+ @it = TrafficLightWithCycleHandler.new(:proceed)
330
+ end
331
+
332
+ it_should_behave_like "a green light with color"
333
+ end
334
+
335
+ describe TrafficLightWithCycleHandler, "when yellow" do
336
+ before :each do
337
+ @it = TrafficLightWithCycleHandler.new(:caution)
338
+ end
339
+
340
+ it_should_behave_like "a yellow light with color"
341
+ end
342
+
343
+ describe TrafficLightWithCycleHandler, "when red" do
344
+ before :each do
345
+ @it = TrafficLightWithCycleHandler.new(:stop)
346
+ end
347
+
348
+ it_should_behave_like "a red light with color"
349
+ end
350
+
351
+ # In fact, the pattern of a handler which executes a state transition is common
352
+ # enough that there is a special syntax for it. Let's convert to using that
353
+ # syntax.
354
+
355
+ # While we're at it, we'll also add a :default keyword to the :green state, and
356
+ # eliminate the initializer.
357
+
358
+ class TrafficLightWithTransitions
359
+ include AlterEgo
360
+
361
+ state :proceed, :default => true do
362
+ handle :color do
363
+ "green"
364
+ end
365
+ transition :to => :caution, :on => :cycle
366
+ end
367
+
368
+ state :caution do
369
+ handle :color do
370
+ "yellow"
371
+ end
372
+ transition :to => :stop, :on => :cycle
373
+ end
374
+
375
+ state :stop do
376
+ handle :color do
377
+ "red"
378
+ end
379
+ transition :to => :proceed, :on => :cycle
380
+ end
381
+ end
382
+
383
+ describe TrafficLightWithTransitions, "by default" do
384
+ before :each do
385
+ @it = TrafficLightWithTransitions.new
386
+ end
387
+
388
+ it "should be in the green state" do
389
+ @it.current_state.should == :proceed
390
+ end
391
+ end
392
+
393
+ describe TrafficLightWithTransitions, "when green" do
394
+ before :each do
395
+ @it = TrafficLightWithTransitions.new
396
+ end
397
+
398
+ it_should_behave_like "a green light with color"
399
+ end
400
+
401
+ describe TrafficLightWithTransitions, "when yellow" do
402
+ before :each do
403
+ @it = TrafficLightWithTransitions.new
404
+ @it.cycle
405
+ end
406
+
407
+ it_should_behave_like "a yellow light with color"
408
+ end
409
+
410
+ describe TrafficLightWithTransitions, "when red" do
411
+ before :each do
412
+ @it = TrafficLightWithTransitions.new
413
+ @it.cycle
414
+ @it.cycle
415
+ end
416
+
417
+ it_should_behave_like "a red light with color"
418
+ end
419
+
420
+ # It is possible to have only some of the states handle a given request. If the
421
+ # method is called while the object is in a state which doesn't handle it, a
422
+ # WrongStateError will be raised.
423
+ #
424
+ # Let's add a #seconds_till_red method to our traffic light, so that it can show
425
+ # a countdown letting motorists know exactly how long they have until the light
426
+ # turns red. Let's say for the sake of example that it will only be valid to
427
+ # call this method when the light is yellow.
428
+
429
+ class TrafficLightWithRedCountdown
430
+ include AlterEgo
431
+
432
+ state :proceed, :default => true do
433
+ handle :color do
434
+ "green"
435
+ end
436
+ transition :to => :caution, :on => :cycle
437
+ end
438
+
439
+ state :caution do
440
+ handle :color do
441
+ "yellow"
442
+ end
443
+ handle :seconds_till_red do
444
+ # ...
445
+ end
446
+ transition :to => :stop, :on => :cycle
447
+ end
448
+
449
+ state :stop do
450
+ handle :color do
451
+ "red"
452
+ end
453
+ transition :to => :proceed, :on => :cycle
454
+ end
455
+ end
456
+
457
+ describe TrafficLightWithRedCountdown, "that is green" do
458
+ before :each do
459
+ @it = TrafficLightWithRedCountdown.new
460
+ end
461
+
462
+ it "should raise an error if #seconds_till_red is called" do
463
+ lambda do
464
+ @it.seconds_till_red
465
+ end.should raise_error(AlterEgo::WrongStateError)
466
+ end
467
+ end
468
+
469
+ describe TrafficLightWithRedCountdown, "that is yellow" do
470
+ before :each do
471
+ @it = TrafficLightWithRedCountdown.new
472
+ @it.cycle
473
+ end
474
+
475
+ it "should raise an error if #seconds_till_red is called" do
476
+ lambda do
477
+ @it.seconds_till_red
478
+ end.should_not raise_error
479
+ end
480
+ end
481
+
482
+ # It is possible to get a list of currently handled requests, as well as a list
483
+ # of all possible requests supported in any state.
484
+
485
+ describe TrafficLightWithRedCountdown do
486
+ before :each do
487
+ @it = TrafficLightWithRedCountdown.new
488
+ end
489
+
490
+ it "should know what requests are supported by states" do
491
+ @it.all_handled_requests.should include(:cycle, :color, :seconds_till_red)
492
+ end
493
+ end
494
+
495
+ # The customer has decided the traffic light must sound an audible alert
496
+ # while in the yellow state, in order to warn vision-impaired pedestrians.
497
+ #
498
+ # In order to accomodate this requirement, we will use on_enter and on_exit
499
+ # handlers to switch an alarm on and off.
500
+
501
+ class TrafficLightWithAlarm
502
+ include AlterEgo
503
+
504
+ state :proceed, :default => true do
505
+ handle :color do
506
+ "green"
507
+ end
508
+ transition :to => :caution, :on => :cycle
509
+ end
510
+
511
+ state :caution do
512
+ on_enter do
513
+ turn_on_alarm
514
+ end
515
+ on_exit do
516
+ turn_off_alarm
517
+ end
518
+ handle :color do
519
+ "yellow"
520
+ end
521
+ handle :seconds_till_red do
522
+ # ...
523
+ end
524
+ transition :to => :stop, :on => :cycle
525
+ end
526
+
527
+ state :stop do
528
+ handle :color do
529
+ "red"
530
+ end
531
+ transition :to => :proceed, :on => :cycle
532
+ end
533
+
534
+ def initialize(hardware_controller)
535
+ @hardware_controller = hardware_controller
536
+ end
537
+
538
+ def turn_on_alarm
539
+ @hardware_controller.alarm_enabled = true
540
+ end
541
+
542
+ def turn_off_alarm
543
+ @hardware_controller.alarm_enabled = false
544
+ end
545
+ end
546
+
547
+ describe TrafficLightWithAlarm do
548
+ it "should not include on_enter or on_exit in list of handlers" do
549
+ TrafficLightWithAlarm.all_handled_requests.should_not include(:on_enter)
550
+ TrafficLightWithAlarm.all_handled_requests.should_not include(:on_exit)
551
+ end
552
+ end
553
+
554
+ describe TrafficLightWithAlarm, "when green" do
555
+ before :each do
556
+ @hardware_controller = mock("Hardware Controller")
557
+ @it = TrafficLightWithAlarm.new(@hardware_controller)
558
+ end
559
+
560
+ it "should enable alarm on transition to yellow" do
561
+ @hardware_controller.should_receive(:alarm_enabled=).
562
+ with(true)
563
+ @it.cycle
564
+ end
565
+ end
566
+
567
+ describe TrafficLightWithAlarm, "when yellow" do
568
+ before :each do
569
+ @hardware_controller = stub("Hardware Controller", :alarm_enabled= => nil)
570
+ @it = TrafficLightWithAlarm.new(@hardware_controller)
571
+ @it.cycle
572
+ end
573
+
574
+ it "should disable alarm on transition to yellow" do
575
+ @hardware_controller.should_receive(:alarm_enabled=).
576
+ with(false)
577
+ @it.cycle
578
+ end
579
+ end
580
+
581
+ # For safety reasons, the light should not allow transitions faster than every
582
+ # twenty seconds. We'll add state guards to ensure this constraint is observed.
583
+ # We'll also add a generic state change action for all states to restart the timer
584
+ # each time the state changes.
585
+
586
+ class TrafficLightWithGuards
587
+ include AlterEgo
588
+
589
+ state :proceed, :default => true do
590
+ handle :color do
591
+ "green"
592
+ end
593
+ transition :to => :caution, :on => :cycle, :if => :min_time_elapsed?
594
+ end
595
+
596
+ state :caution do
597
+ on_enter do
598
+ turn_on_alarm
599
+ end
600
+ on_exit do
601
+ turn_off_alarm
602
+ end
603
+ handle :color do
604
+ "yellow"
605
+ end
606
+ handle :seconds_till_red do
607
+ # ...
608
+ end
609
+ transition :to => :stop, :on => :cycle, :if => :min_time_elapsed?
610
+ end
611
+
612
+ state :stop do
613
+ handle :color do
614
+ "red"
615
+ end
616
+
617
+ # Just to demonstrate that it is possible, we use a proc here instead of a
618
+ # symbol
619
+ transition(:to => :proceed, :on => :cycle, :if => proc { min_time_elapsed? })
620
+ end
621
+
622
+ # On state change
623
+ request_filter :state => any, :request => any, :new_state => not_nil do
624
+ @hardware_controller.restart_timer
625
+ end
626
+
627
+ def initialize(hardware_controller)
628
+ @hardware_controller = hardware_controller
629
+ end
630
+
631
+ def turn_on_alarm
632
+ @hardware_controller.alarm_enabled = true
633
+ end
634
+
635
+ def turn_off_alarm
636
+ @hardware_controller.alarm_enabled = false
637
+ end
638
+
639
+ def min_time_elapsed?
640
+ @hardware_controller.time_elapsed >= 20
641
+ end
642
+ end
643
+
644
+ describe TrafficLightWithGuards, "that is green" do
645
+ before :each do
646
+ @hardware_controller = stub("Hardware Controller",
647
+ :time_elapsed => 21,
648
+ :restart_timer => nil,
649
+ :alarm_enabled= => nil)
650
+ @it = TrafficLightWithGuards.new(@hardware_controller)
651
+ end
652
+
653
+ it "should check the hardware controller's #time_elapsed on cycle" do
654
+ @hardware_controller.should_receive(:time_elapsed).and_return(19)
655
+ @it.cycle
656
+ end
657
+
658
+ it "should fail to cycle if elapsed time < 20 seconds" do
659
+ @hardware_controller.stub!(:time_elapsed).and_return(19)
660
+ @it.cycle.should be_false
661
+ @it.current_state.should == :proceed
662
+ end
663
+
664
+ it "should cycle if elapsed time >= 20 seconds" do
665
+ @hardware_controller.stub!(:time_elapsed).and_return(20)
666
+ @it.cycle.should be_true
667
+ @it.current_state.should == :caution
668
+ end
669
+
670
+ it "should restart the timer on state change" do
671
+ @hardware_controller.should_receive(:restart_timer)
672
+ @it.cycle
673
+ end
674
+
675
+ it "should restart the timer on state change" do
676
+ @hardware_controller.should_receive(:restart_timer)
677
+ @it.cycle
678
+ end
679
+ end
680
+
681
+ describe TrafficLightWithGuards, "that is yellow" do
682
+ before :each do
683
+ @hardware_controller = stub("Hardware Controller",
684
+ :time_elapsed => 21,
685
+ :restart_timer => nil,
686
+ :alarm_enabled= => nil)
687
+ @it = TrafficLightWithGuards.new(@hardware_controller)
688
+ @it.cycle
689
+ end
690
+
691
+ it "should check the hardware controller's #time_elapsed on cycle" do
692
+ @hardware_controller.should_receive(:time_elapsed).and_return(19)
693
+ @it.cycle
694
+ end
695
+
696
+ it "should fail to cycle if elapsed time < 20 seconds" do
697
+ @hardware_controller.stub!(:time_elapsed).and_return(19)
698
+ @it.cycle.should be_false
699
+ end
700
+
701
+ it "should remain in :caution state if elapsed time < 20" do
702
+ @hardware_controller.stub!(:time_elapsed).and_return(19)
703
+ @it.cycle
704
+ @it.current_state.should == :caution
705
+ end
706
+
707
+ it "should cycle if elapsed time >= 20 seconds" do
708
+ @hardware_controller.stub!(:time_elapsed).and_return(20)
709
+ @it.cycle.should be_true
710
+ end
711
+
712
+ it "should cycle to :stop state if elapsed time >= 20 seconds" do
713
+ @hardware_controller.stub!(:time_elapsed).and_return(20)
714
+ @it.cycle
715
+ @it.current_state.should == :stop
716
+ end
717
+
718
+ it "should restart the timer on state change" do
719
+
720
+ @hardware_controller.should_receive(:restart_timer)
721
+ @it.cycle
722
+ end
723
+ end
724
+
725
+ describe TrafficLightWithGuards, "that is red" do
726
+ before :each do
727
+ @hardware_controller = stub("Hardware Controller",
728
+ :time_elapsed => 21,
729
+ :restart_timer => nil,
730
+ :alarm_enabled= => nil)
731
+ @it = TrafficLightWithGuards.new(@hardware_controller)
732
+ @it.cycle
733
+ @it.cycle
734
+ end
735
+
736
+ it "should fail to cycle if elapsed time < 20 seconds" do
737
+ @hardware_controller.stub!(:time_elapsed).and_return(19)
738
+ @it.cycle.should be_false
739
+ @it.current_state.should == :stop
740
+ end
741
+
742
+ it "should cycle if elapsed time >= 20 seconds" do
743
+ @hardware_controller.stub!(:time_elapsed).and_return(20)
744
+ @it.cycle.should be_true
745
+ @it.current_state.should == :proceed
746
+ end
747
+
748
+ end
749
+
750
+ # The traffic light controller actually stores it's current state as three
751
+ # discrete booleans, one for each light which should be either on or off. We'll
752
+ # customize the state saving and loading methods in order to support this
753
+ # arrangement.
754
+
755
+ class TrafficLightWithCustomStorage
756
+ include AlterEgo
757
+
758
+ state :proceed, :default => true do
759
+ handle :color do
760
+ "green"
761
+ end
762
+ transition :to => :caution, :on => :cycle
763
+ end
764
+
765
+ state :caution do
766
+ on_enter do
767
+ turn_on_alarm
768
+ end
769
+ on_exit do
770
+ turn_off_alarm
771
+ end
772
+ handle :color do
773
+ "yellow"
774
+ end
775
+ handle :seconds_till_red do
776
+ # ...
777
+ end
778
+ transition :to => :stop, :on => :cycle
779
+ end
780
+
781
+ state :stop do
782
+ handle :color do
783
+ "red"
784
+ end
785
+ transition :to => :proceed, :on => :cycle
786
+ end
787
+
788
+ def initialize(hardware_controller)
789
+ @hardware_controller = hardware_controller
790
+ end
791
+
792
+ def turn_on_alarm
793
+ @hardware_controller.alarm_enabled = true
794
+ end
795
+
796
+ def turn_off_alarm
797
+ @hardware_controller.alarm_enabled = false
798
+ end
799
+
800
+ def state
801
+ gyr = [
802
+ @hardware_controller.green,
803
+ @hardware_controller.yellow,
804
+ @hardware_controller.red
805
+ ]
806
+
807
+ case gyr
808
+ when [true, false, false] then :proceed
809
+ when [false, true, false] then :caution
810
+ when [false, false, true] then :stop
811
+ else raise "Invalid state!"
812
+ end
813
+ end
814
+
815
+ def state=(value)
816
+ gyr = case value
817
+ when :proceed then [true, false, false]
818
+ when :caution then [false, true, false]
819
+ when :stop then [false, false, true]
820
+ end
821
+ @hardware_controller.green = gyr[0]
822
+ @hardware_controller.yellow = gyr[1]
823
+ @hardware_controller.red = gyr[2]
824
+ end
825
+ end
826
+
827
+
828
+ describe TrafficLightWithCustomStorage, "that is green" do
829
+ before :each do
830
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
831
+ :restart_timer => nil,
832
+ :green => true,
833
+ :yellow => false,
834
+ :red => false)
835
+ @it = TrafficLightWithCustomStorage.new(@hardware_controller)
836
+ end
837
+
838
+ it "should be in the proceed state" do
839
+ @it.current_state.should == :proceed
840
+ end
841
+
842
+ it "should set lights for yellow on cycle" do
843
+ @it.cycle
844
+ @hardware_controller.green.should be_false
845
+ @hardware_controller.yellow.should be_true
846
+ @hardware_controller.red.should be_false
847
+ end
848
+ end
849
+
850
+ describe TrafficLightWithCustomStorage, "that is yellow" do
851
+ before :each do
852
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
853
+ :restart_timer => nil,
854
+ :green => false,
855
+ :yellow => true,
856
+ :red => false)
857
+ @it = TrafficLightWithCustomStorage.new(@hardware_controller)
858
+ end
859
+
860
+ it "should be in the caution state" do
861
+ @it.current_state.should == :caution
862
+ end
863
+
864
+ it "should set lights for red on cycle" do
865
+ @it.cycle
866
+ @hardware_controller.green.should be_false
867
+ @hardware_controller.yellow.should be_false
868
+ @hardware_controller.red.should be_true
869
+ end
870
+
871
+ end
872
+
873
+ describe TrafficLightWithCustomStorage, "that is red" do
874
+ before :each do
875
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
876
+ :restart_timer => nil,
877
+ :green => false,
878
+ :yellow => false,
879
+ :red => true)
880
+ @it = TrafficLightWithCustomStorage.new(@hardware_controller)
881
+ end
882
+
883
+ it "should be in the stop state" do
884
+ @it.current_state.should == :stop
885
+ end
886
+
887
+ it "should set lights for green on cycle" do
888
+ @it.cycle
889
+ @hardware_controller.green.should be_true
890
+ @hardware_controller.yellow.should be_false
891
+ @hardware_controller.red.should be_false
892
+ end
893
+
894
+ end
895
+
896
+ # In order to integrate with a pedestrian traffic light, our light needs to send
897
+ # a signal whenever it changes. We'll add a transition action to handle this.
898
+ #
899
+ # It also needs to flash a strobe when transitioning to yellow or red. We'll
900
+ # use a request filter and a state matching pattern to accomplish this.
901
+
902
+ class TrafficLightWithTransAction
903
+ include AlterEgo
904
+
905
+ state :proceed, :default => true do
906
+ handle :color do
907
+ "green"
908
+ end
909
+ transition :to => :caution, :on => :cycle do
910
+ @hardware_controller.notify(:yellow)
911
+ end
912
+ end
913
+
914
+ state :caution do
915
+ on_enter do
916
+ turn_on_alarm
917
+ end
918
+ on_exit do
919
+ turn_off_alarm
920
+ end
921
+ handle :color do
922
+ "yellow"
923
+ end
924
+ handle :seconds_till_red do
925
+ # ...
926
+ end
927
+ transition :to => :stop, :on => :cycle do
928
+ @hardware_controller.notify(:red)
929
+ end
930
+ end
931
+
932
+ state :stop do
933
+ handle :color do
934
+ "red"
935
+ end
936
+ transition :to => :proceed, :on => :cycle do
937
+ @hardware_controller.notify(:green)
938
+ end
939
+ end
940
+
941
+ request_filter :state => any,
942
+ :request => any,
943
+ :new_state => [:caution, :stop] do
944
+ @hardware_controller.flash_strobe
945
+ end
946
+
947
+ def initialize(hardware_controller)
948
+ @hardware_controller = hardware_controller
949
+ end
950
+
951
+ def turn_on_alarm
952
+ @hardware_controller.alarm_enabled = true
953
+ end
954
+
955
+ def turn_off_alarm
956
+ @hardware_controller.alarm_enabled = false
957
+ end
958
+
959
+ def state
960
+ gyr = [
961
+ @hardware_controller.green,
962
+ @hardware_controller.yellow,
963
+ @hardware_controller.red
964
+ ]
965
+
966
+ case gyr
967
+ when [true, false, false] then :proceed
968
+ when [false, true, false] then :caution
969
+ when [false, false, true] then :stop
970
+ else raise "Invalid state!"
971
+ end
972
+ end
973
+
974
+ def state=(value)
975
+ gyr = case value
976
+ when :proceed then [true, false, false]
977
+ when :caution then [false, true, false]
978
+ when :stop then [false, false, true]
979
+ end
980
+ @hardware_controller.green = gyr[0]
981
+ @hardware_controller.yellow = gyr[1]
982
+ @hardware_controller.red = gyr[2]
983
+ end
984
+ end
985
+
986
+ describe TrafficLightWithTransAction, "that is green" do
987
+ before :each do
988
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
989
+ :restart_timer => nil,
990
+ :green => true,
991
+ :yellow => false,
992
+ :red => false)
993
+ @hardware_controller.stub!(:notify)
994
+ @it = TrafficLightWithTransAction.new(@hardware_controller)
995
+ end
996
+
997
+ it "should notify that it has turned yellow on cycle" do
998
+ @hardware_controller.should_receive(:notify).with(:yellow)
999
+ @it.cycle
1000
+ end
1001
+
1002
+ it "should flash strobe on cycle to yellow" do
1003
+ @hardware_controller.should_receive(:flash_strobe)
1004
+ @it.cycle
1005
+ end
1006
+ end
1007
+
1008
+
1009
+ describe TrafficLightWithTransAction, "that is yellow" do
1010
+ before :each do
1011
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
1012
+ :restart_timer => nil,
1013
+ :green => false,
1014
+ :yellow => true,
1015
+ :red => false)
1016
+ @hardware_controller.stub!(:notify)
1017
+ @it = TrafficLightWithTransAction.new(@hardware_controller)
1018
+ end
1019
+
1020
+ it "should notify that it has turned red on cycle" do
1021
+ @hardware_controller.should_receive(:notify).with(:red)
1022
+ @it.cycle
1023
+ end
1024
+
1025
+ it "should flash strobe on cycle to red" do
1026
+ @hardware_controller.should_receive(:flash_strobe)
1027
+ @it.cycle
1028
+ end
1029
+ end
1030
+
1031
+ describe TrafficLightWithTransAction, "that is red" do
1032
+ before :each do
1033
+ @hardware_controller = OpenStruct.new( :time_elapsed => 21,
1034
+ :restart_timer => nil,
1035
+ :green => false,
1036
+ :yellow => false,
1037
+ :red => true)
1038
+ @hardware_controller.stub!(:notify)
1039
+ @it = TrafficLightWithTransAction.new(@hardware_controller)
1040
+ end
1041
+
1042
+ it "should notify that it has turned green on cycle" do
1043
+ @hardware_controller.should_receive(:notify).with(:green)
1044
+ @it.cycle
1045
+ end
1046
+
1047
+ it "should not flash strobe on cycle to green" do
1048
+ @hardware_controller.should_not_receive(:flash_strobe)
1049
+ end
1050
+
1051
+ end