vmsim 0.2.3

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.
Files changed (52) hide show
  1. data/.autotest +27 -0
  2. data/README.txt +1 -0
  3. data/Rakefile +40 -0
  4. data/VERSION +1 -0
  5. data/bin/vmdeploy +23 -0
  6. data/bin/vmsim +37 -0
  7. data/doc/actuators_and_sensors.txt +32 -0
  8. data/doc/handout.odt +0 -0
  9. data/doc/machine.jpg +0 -0
  10. data/doc/todo.txt +25 -0
  11. data/lib/adapter/actuator_collection.rb +61 -0
  12. data/lib/adapter/sensor_collection.rb +57 -0
  13. data/lib/archive_builder.rb +64 -0
  14. data/lib/control/main.rb +21 -0
  15. data/lib/control.rb +3 -0
  16. data/lib/deploy/deployer.rb +96 -0
  17. data/lib/deploy.rb +1 -0
  18. data/lib/devices/devices.rb +73 -0
  19. data/lib/devices.rb +1 -0
  20. data/lib/gui/SimulatorGui.gtk +289 -0
  21. data/lib/gui/simulator_gtk.rb +57 -0
  22. data/lib/gui/simulator_gui.rb +64 -0
  23. data/lib/gui.rb +2 -0
  24. data/lib/hardware/bin.rb +28 -0
  25. data/lib/hardware/button.rb +18 -0
  26. data/lib/hardware/can.rb +9 -0
  27. data/lib/hardware/cash_register.rb +79 -0
  28. data/lib/hardware/component.rb +56 -0
  29. data/lib/hardware/display.rb +29 -0
  30. data/lib/hardware/drawer.rb +45 -0
  31. data/lib/hardware/enum.rb +20 -0
  32. data/lib/hardware/simulator.rb +98 -0
  33. data/lib/hardware.rb +10 -0
  34. data/lib/hardware_adapter.rb +11 -0
  35. data/lib/vmlog.rb +5 -0
  36. data/test/adapter/actuator_collection_test.rb +106 -0
  37. data/test/adapter/sensor_collection_test.rb +51 -0
  38. data/test/deploy/deployer_test.rb +114 -0
  39. data/test/deploy/was_run.sh +5 -0
  40. data/test/devices/devices_test.rb +78 -0
  41. data/test/hardware/bin_test.rb +57 -0
  42. data/test/hardware/button_test.rb +23 -0
  43. data/test/hardware/can_test.rb +11 -0
  44. data/test/hardware/cash_register_test.rb +157 -0
  45. data/test/hardware/component_test.rb +37 -0
  46. data/test/hardware/display_test.rb +94 -0
  47. data/test/hardware/drawer_test.rb +100 -0
  48. data/test/hardware/enum_test.rb +33 -0
  49. data/test/hardware/simulator_test.rb +189 -0
  50. data/test/test_helper.rb +5 -0
  51. data/vmsim.gemspec +89 -0
  52. metadata +131 -0
@@ -0,0 +1,20 @@
1
+ module Hardware
2
+ class Enum
3
+ def initialize(type)
4
+ @type = type
5
+ end
6
+ def inspect
7
+ return "#{self.class.name}.#{@type}"
8
+ end
9
+ def self.method_missing(method)
10
+ begin
11
+ self.send('class_variable_get',"@@#{method}")
12
+ rescue
13
+ raise "Enum constant '#{method}' not defined in #{self}"
14
+ end
15
+ end
16
+ def self.values(*values)
17
+ values.each{|value| send('class_variable_set',"@@#{value}", new(value.to_s))}
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ require 'hardware_adapter'
2
+ module Hardware
3
+ class Simulator
4
+ attr_reader :bin, :cash_register, :display
5
+
6
+ def initialize(actuators = Adapter.actuators, sensors = Adapter.sensors)
7
+ @components = {}
8
+ @actuators = actuators
9
+ @sensors = sensors
10
+ end
11
+
12
+ def component(component_name)
13
+ @components[component_name]
14
+ end
15
+
16
+ def assemble_hardware_component(component_name, component)
17
+ @components[component_name] = component
18
+ component.configure
19
+ end
20
+
21
+ def assemble_hardware_components(basename, components)
22
+ components.each_with_index do |component, sequence_number|
23
+ assemble_hardware_component(component_name(basename, sequence_number), component)
24
+ end
25
+ end
26
+
27
+ def monitor_changes(component_name, &block)
28
+ component(component_name).monitor_changes(&block)
29
+ end
30
+
31
+ def assemble_hardware
32
+ assemble_hardware_component :bin, create_bin
33
+ assemble_hardware_component :display, create_display
34
+ assemble_hardware_component :cash_register, create_cash_register
35
+ assemble_hardware_components :drawer, create_drawers
36
+ assemble_hardware_components :button, create_buttons
37
+ end
38
+
39
+ def drawer(no)
40
+ @drawers[no]
41
+ end
42
+
43
+ def button(no)
44
+ @buttons[no]
45
+ end
46
+
47
+ def reset
48
+ @components.each { |name, component| component.reset }
49
+ sensors.remove_fire_listeners
50
+ boot
51
+ end
52
+
53
+ def boot
54
+ call_boot_block
55
+ end
56
+
57
+ def on_boot(&block)
58
+ @boot_block = block
59
+ end
60
+
61
+
62
+ private
63
+ attr_reader :actuators, :sensors
64
+
65
+ def call_boot_block
66
+ @boot_block.call if @boot_block
67
+ end
68
+ def component_name(basename, sequence_number)
69
+ "#{basename}_#{sequence_number}".to_sym
70
+ end
71
+
72
+ def create_display
73
+ @display = Hardware::Display.new(actuators)
74
+ end
75
+
76
+ def create_cash_register
77
+ @cash_register = Hardware::CashRegister.new(bin, sensors, actuators)
78
+ end
79
+
80
+ def create_drawers
81
+ @drawers = [ Hardware::Drawer.new(bin,sensors,actuators, 0),
82
+ Hardware::Drawer.new(bin,sensors,actuators, 1),
83
+ Hardware::Drawer.new(bin,sensors,actuators, 2),
84
+ Hardware::Drawer.new(bin,sensors,actuators, 3)]
85
+ end
86
+
87
+ def create_buttons
88
+ @buttons = [ Hardware::Button.new(sensors, 0),
89
+ Hardware::Button.new(sensors, 1),
90
+ Hardware::Button.new(sensors, 2),
91
+ Hardware::Button.new(sensors, 3)]
92
+ end
93
+
94
+ def create_bin
95
+ @bin = Hardware::Bin.new(sensors)
96
+ end
97
+ end
98
+ end
data/lib/hardware.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'hardware/enum'
2
+ require 'hardware/component'
3
+ require 'hardware/can'
4
+ require 'hardware/button'
5
+ require 'hardware/display'
6
+ require 'hardware/bin'
7
+ require 'hardware/drawer'
8
+ require 'hardware/cash_register'
9
+ require 'hardware/simulator'
10
+
@@ -0,0 +1,11 @@
1
+ require 'adapter/sensor_collection'
2
+ require 'adapter/actuator_collection'
3
+
4
+ module Adapter
5
+ def self.actuators
6
+ @@actuators ||= ActuatorCollection.new
7
+ end
8
+ def self.sensors
9
+ @@sensors ||= SensorCollection.new
10
+ end
11
+ end
data/lib/vmlog.rb ADDED
@@ -0,0 +1,5 @@
1
+ module VMLog
2
+ def log(message)
3
+ $stderr.puts message
4
+ end
5
+ end
@@ -0,0 +1,106 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'hardware_adapter'
3
+
4
+ module Adapter
5
+ class ActuatorCollectionTest < Test::Unit::TestCase
6
+ attr_reader :actuator_collection, :actuator_called, :other_actuator_called
7
+ def setup
8
+ @actuator_collection = ActuatorCollection.new(SynchronousFirer)
9
+ @actuator_called = false
10
+ @other_actuator_called = false
11
+ actuator_collection.create_actuator_for('some_actuator') do
12
+ @actuator_called = true
13
+ end
14
+ actuator_collection.create_actuator_for(:some_other_actuator) do
15
+ @other_actuator_called = true
16
+ end
17
+ end
18
+
19
+ def test_list_lists_actuators
20
+ assert_equal [:some_actuator, :some_other_actuator], actuator_collection.list
21
+ end
22
+
23
+ def test_actuators_not_called_if_not_fired
24
+ assert_equal false, actuator_called, "this actuator should not be called yet"
25
+ assert_equal false, other_actuator_called, "this actuator should not be called yet"
26
+ end
27
+
28
+ def test_calls_block_given_for_any_actuator
29
+ actuator_collection.fire(:some_actuator)
30
+ actuator_collection.fire(:some_other_actuator)
31
+
32
+ assert actuator_called, "actuator should be called on fire"
33
+ assert other_actuator_called, "actuator should be called on fire"
34
+ end
35
+
36
+ def test_actuator_block_can_accept_parameters
37
+ int_val = -1
38
+ string_val = ""
39
+ actuator_collection.create_actuator_for(:parametarized_actuator) do |param1, param2|
40
+ int_val = param1
41
+ string_val = param2
42
+ end
43
+ actuator_collection.fire(:parametarized_actuator, 34, "gijs")
44
+
45
+ assert_equal 34, int_val
46
+ assert_equal "gijs", string_val
47
+ end
48
+
49
+ def test_actuator_block_may_skip_parameters
50
+ int_val = -1
51
+ string_val = ""
52
+ actuator_collection.create_actuator_for(:parametarized_actuator) do |param1, param2|
53
+ int_val = param1
54
+ string_val = param2
55
+ end
56
+ actuator_collection.fire(:parametarized_actuator, 34, "gijs", "truus")
57
+
58
+ assert_equal 34, int_val
59
+ assert_equal "gijs", string_val
60
+ end
61
+
62
+ def test_raises_exception_on_firing_unknown_actuator
63
+ assert_raises NoSuchActuatorException do
64
+ actuator_collection.fire(:unknown_actuator)
65
+ end
66
+ end
67
+
68
+ def test_raises_exception_on_no_block_given
69
+ assert_raises NoActuatorBlockGiven do
70
+ actuator_collection.create_actuator_for(:actuator_no_block)
71
+ end
72
+ end
73
+
74
+ def test_raises_exception_on_duplicate_actuator
75
+ assert_raises DuplicateActuatorException do
76
+ actuator_collection.create_actuator_for(:some_actuator) {}
77
+ end
78
+ end
79
+ end
80
+
81
+ class AsynchronousFirerTest < Test::Unit::TestCase
82
+ def test_fires_on_other_thread
83
+ fired = false
84
+ firer = AsynchronousFirer.new do
85
+ sleep(0.1)
86
+ fired = true
87
+ end
88
+ t = firer.fire
89
+ assert_equal false, fired, "should not be fired"
90
+ t.join
91
+ assert fired, "should be fired"
92
+ end
93
+
94
+ def test_fires_on_other_thread_with_parameters
95
+ fired = ''
96
+ firer = AsynchronousFirer.new do |value|
97
+ sleep(0.1)
98
+ fired = value
99
+ end
100
+ t = firer.fire('passed string value')
101
+ assert_equal '', fired, "should not be fired"
102
+ t.join
103
+ assert_equal 'passed string value', fired, "should be fired"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,51 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'hardware_adapter'
3
+
4
+ module Adapter
5
+
6
+ class SensorCollectionFireTest < Test::Unit::TestCase
7
+ attr_reader :sensors
8
+ def setup
9
+ @sensors = SensorCollection.new
10
+ sensors.create_sensor_for(:bin_entry)
11
+ end
12
+
13
+ def test_list_lists_sensor
14
+ sensors.create_sensor_for(:something_entry)
15
+ assert_equal [:bin_entry, :something_entry], sensors.list
16
+ end
17
+
18
+ def test_calls_block_given_for_a_sensor
19
+ bin_entry_fired = false
20
+ sensors.on(:bin_entry) do
21
+ bin_entry_fired = true
22
+ end
23
+ sensors.fire(:bin_entry)
24
+ assert bin_entry_fired, "bin_entry should be fired"
25
+ end
26
+
27
+ def test_swallows_fire_silently_when_no_handler_defined
28
+ sensors.fire(:bin_entry)
29
+ end
30
+
31
+ end
32
+
33
+ class SensorCollectionFireWithoutReceptorsCreatedTest < Test::Unit::TestCase
34
+ attr_reader :sensors
35
+ def setup
36
+ @sensors = SensorCollection.new
37
+ end
38
+
39
+ def test_swallows_fire_silently_when_no_handler_defined
40
+ sensors.fire(:bin_entry)
41
+ end
42
+
43
+ def test_on_raises_a_no_such_sensor_exception
44
+ assert_raises NoSuchSensorException do
45
+ sensors.on(:bin_entry) {}
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,114 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'deploy'
3
+ require 'tmpdir'
4
+ require 'archive_builder'
5
+
6
+ module Deploy
7
+ module DeployerTestHelper
8
+ attr_reader :deployer
9
+ def create_deployer
10
+ @deployer = Deployer.new(File.expand_path(File.join(File.dirname(__FILE__), 'was_run.sh')))
11
+ end
12
+ def deploy(archive)
13
+ create_deployer
14
+ deployer.deploy archive
15
+ end
16
+ def is_running(pid)
17
+ begin
18
+ Process.kill(0, pid)
19
+ return true
20
+ rescue
21
+ return false
22
+ end
23
+ end
24
+ end
25
+
26
+ class DeployerDeployTest < Test::Unit::TestCase
27
+ include ArchiveBuilder
28
+ include DeployerTestHelper
29
+ include FileUtils
30
+ def teardown
31
+ rm_r './tmp' if File.exists?('./tmp')
32
+ end
33
+
34
+ def test_unzips_the_archive_and_fires_the_executable
35
+ deploy an_archive('controlcode.pkg') {
36
+ a_file("main.rb").with("puts 'was unpacked'")
37
+ }
38
+ assert is_running(deployer.current_pid)
39
+ assert_match /was started.*was unpacked/m, deployer.wait_for.run_log
40
+ end
41
+
42
+ def test_unzips_the_archive_with_relative_path_and_fires_the_executable
43
+ deploy an_archive('controlcode.pkg', 'tmp') {
44
+ a_file("main.rb").with("puts 'was unpacked'")
45
+ }
46
+ assert is_running(deployer.current_pid)
47
+ assert_match /was started.*was unpacked/m, deployer.wait_for.run_log
48
+ end
49
+
50
+ def test_clears_current_pid_on_exit
51
+ deploy an_archive('controlcode.pkg') {
52
+ a_file("main.rb").with("puts 'was unpacked'")
53
+ }
54
+ assert_nil deployer.wait_for.current_pid
55
+ assert !File.exists?(Deployer::PIDFILE), "PIDFILE should be gone"
56
+ end
57
+
58
+ def test_removes_previous_files
59
+ deploy an_archive('controlcode.pkg') {
60
+ a_file("loaded_class.rb").with("class LoadedClass; end")
61
+ a_file("main.rb").with("require 'loaded_class'")
62
+ }
63
+
64
+ deploy an_archive('controlcode.pkg') {
65
+ a_file("main.rb").with("puts 'was unpacked'")
66
+ }
67
+
68
+ assert !File.exists?(File.join(Deployer::DEPLOYED_DIR, 'loaded_class.rb')), "loaded class should be removed"
69
+ end
70
+
71
+ def test_removes_previous_directories
72
+ deploy an_archive('controlcode.pkg') {
73
+ a_file("lib/loaded_class.rb").with("class LoadedClass; end")
74
+ a_file("main.rb").with("require 'lib/loaded_class'")
75
+ }
76
+
77
+ deploy an_archive('controlcode.pkg') {
78
+ a_file("main.rb").with("puts 'was unpacked'")
79
+ }
80
+
81
+ assert !File.exists?(File.join(Deployer::DEPLOYED_DIR, 'loaded_class.rb')), "loaded class should be removed"
82
+ end
83
+
84
+ def test_kills_the_current_running_process_and_starts_new_one
85
+ deploy an_archive('controlcode.pkg') {
86
+ a_file("main.rb").with("sleep 1")
87
+ }
88
+ old_process_id = deployer.current_pid
89
+
90
+ deploy an_archive('controlcode.pkg') {
91
+ a_file("main.rb").with("puts 'was unpacked'")
92
+ }
93
+
94
+ assert !is_running(old_process_id), "process should be stopped before starting new"
95
+ end
96
+
97
+ end
98
+
99
+ class DeployerKillTest < Test::Unit::TestCase
100
+ include ArchiveBuilder
101
+ include DeployerTestHelper
102
+
103
+ def test_kills_the_current_running_process
104
+ deploy an_archive('controlcode.pkg') {
105
+ a_file("main.rb").with("sleep 20")
106
+ }
107
+ process_id = deployer.current_pid
108
+ create_deployer
109
+ deployer.kill
110
+ assert !is_running(process_id), "process should be stopped"
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('~/.vendingmachine/control')
3
+ puts "was started"
4
+
5
+ require 'main'
@@ -0,0 +1,78 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'mocha'
3
+ require 'devices'
4
+
5
+ module Devices
6
+ class DevicesTest < Test::Unit::TestCase
7
+ attr_reader :actuators, :sensors, :devices, :handler
8
+ def setup
9
+ @actuators = mock('actuatorcollection')
10
+ @sensors = mock('sensorcollection')
11
+ @devices = Devices.new(actuators, sensors)
12
+ @handler = 'not called'
13
+ end
14
+
15
+ def test_display_show
16
+ actuators.expects(:fire).with(:display_show, 1, "test message")
17
+ devices.display_show(1, "test message")
18
+ end
19
+
20
+ def test_button_on_press
21
+ sensors.expects(:on).with(:button_press_1).yields
22
+ devices.on_button_pressed(1) { handler_called }
23
+ assert_equal 'called', handler
24
+ end
25
+
26
+ def test_bin_entry
27
+ sensors.expects(:on).with(:bin_entry).yields
28
+ devices.on_bin_entry { handler_called }
29
+ assert_equal 'called', handler
30
+ end
31
+
32
+ def test_bin_fetch_all
33
+ sensors.expects(:on).with(:bin_fetch_all).yields
34
+ devices.on_bin_fetch_all { handler_called }
35
+ assert_equal 'called', handler
36
+ end
37
+
38
+ def test_cash_register_fill_money
39
+ sensors.expects(:on).with(:cash_fill_100).yields
40
+ devices.on_cash_filled(100) { handler_called }
41
+ assert_equal 'called', handler
42
+ end
43
+
44
+ def test_cash_register_insert_money
45
+ sensors.expects(:on).with(:cash_insert_100).yields
46
+ devices.on_cash_inserted(100) { handler_called }
47
+ assert_equal 'called', handler
48
+ end
49
+
50
+ def test_cash_drop_drops_coin
51
+ actuators.expects(:fire).with(:cash_drop_100)
52
+ devices.cash_drop_coin(100)
53
+ end
54
+
55
+ def test_drawer_register_can_dropped
56
+ sensors.expects(:on).with(:drawer_drop_can_0).yields
57
+ devices.on_drawer_dropped(0) { handler_called }
58
+ assert_equal 'called', handler
59
+ end
60
+
61
+ def test_drawer_register_can_filled
62
+ sensors.expects(:on).with(:drawer_fill_can_0).yields
63
+ devices.on_drawer_filled(0) { handler_called }
64
+ assert_equal 'called', handler
65
+ end
66
+
67
+ def test_drawer_drop_drops_can
68
+ actuators.expects(:fire).with(:drawer_drop_can_1)
69
+ devices.drawer_drop_can(1)
70
+ end
71
+
72
+ private
73
+
74
+ def handler_called
75
+ @handler = 'called'
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'mocha'
3
+ require 'hardware'
4
+ require 'hardware_adapter'
5
+
6
+ module Hardware
7
+ class BinTest < Test::Unit::TestCase
8
+ attr_reader :bin, :sensor_collection
9
+ def setup
10
+ @sensor_collection = mock('sensor_collection')
11
+ @bin = Bin.new(sensor_collection)
12
+ end
13
+
14
+ def test_receive_fills_the_bin_with_the_item
15
+ sensor_collection.expects(:fire).with(:bin_entry)
16
+ bin.receive('some_object')
17
+ assert_equal(['some_object'], bin.contents)
18
+ end
19
+
20
+ def test_fetch_the_stuff_empties_the_bin
21
+ sensor_collection.stubs(:fire).with(:bin_entry) # firing :bin_entry tested above
22
+ bin.receive('some_object')
23
+ bin.receive('some_object')
24
+
25
+ sensor_collection.expects(:fire).with(:bin_fetch_all)
26
+ bin.fetch_all
27
+
28
+ assert_equal([], bin.contents)
29
+ end
30
+
31
+ def test_configure_configures_the_sensor_in_the_sensor_collection
32
+ sensor_collection.expects(:create_sensor_for).with(:bin_entry)
33
+ sensor_collection.expects(:create_sensor_for).with(:bin_fetch_all)
34
+ bin.configure
35
+ end
36
+
37
+ def test_updates_ui_when_receiving_something
38
+ bin = Bin.new(Adapter::SensorCollection.null)
39
+ change_called = false
40
+ bin.monitor_changes do
41
+ change_called = true
42
+ end
43
+ bin.receive('some_object')
44
+ assert change_called, 'change was not called'
45
+ end
46
+
47
+ def test_updates_ui_when_fetching_all
48
+ bin = Bin.new(Adapter::SensorCollection.null)
49
+ change_called = false
50
+ bin.monitor_changes do
51
+ change_called = true
52
+ end
53
+ bin.fetch_all
54
+ assert change_called, 'change was not called'
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'mocha'
3
+ require 'hardware'
4
+
5
+ module Hardware
6
+ class ButtonTest < Test::Unit::TestCase
7
+ attr_reader :sensor_collection, :button
8
+ def setup
9
+ @sensor_collection = mock('sensor_collection')
10
+ @button = Button.new(sensor_collection, 1)
11
+ end
12
+
13
+ def test_press_fires_sensor
14
+ sensor_collection.expects(:fire).with(:button_press_1)
15
+ button.press
16
+ end
17
+
18
+ def test_configure_configures_sensor_for_button_press
19
+ sensor_collection.expects(:create_sensor_for).with(:button_press_1)
20
+ button.configure
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__),'..','test_helper')
2
+ require 'hardware'
3
+
4
+ module Hardware
5
+ class CanTest < Test::Unit::TestCase
6
+ def test_to_s_shows_can_name
7
+ assert_equal 'can of cola', Can.cola.to_s
8
+ assert_equal 'can of fanta', Can.fanta.to_s
9
+ end
10
+ end
11
+ end