alter-ego 1.0.0

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,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