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
data/.autotest ADDED
@@ -0,0 +1,27 @@
1
+ class Autotest
2
+ def path_to_classname(s)
3
+ sep = File::SEPARATOR
4
+ f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
5
+ f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
6
+
7
+ f.join('::')
8
+ end
9
+ end
10
+
11
+
12
+ Autotest.add_hook :initialize do |autotest|
13
+ autotest.add_mapping(%r%^test/.*\.rb$%) do |filename, _|
14
+ filename
15
+ end
16
+ autotest.add_mapping(%r%^lib/(.*)\.rb$%) do |_, m|
17
+ ["test/#{m[1]}_test.rb"]
18
+ end
19
+ autotest.add_mapping(%r%^test/test_helper.rb$%) do
20
+ files_matching %r%^test/.*_test\.rb$%
21
+ end
22
+ end
23
+
24
+
25
+ Autotest.add_discovery do
26
+ "testunit"
27
+ end
data/README.txt ADDED
@@ -0,0 +1 @@
1
+ vendingmachine exercise in Ruby
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ #Gem::manage_gems
3
+ require 'rubygems/package_task'
4
+ require 'rake/testtask'
5
+
6
+
7
+ desc 'Running all unit tests'
8
+ task :default => :test
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.test_files = FileList['test/**/*test.rb', 'test/*test.rb']
12
+ end
13
+
14
+ desc 'Measures test coverage'
15
+ task :coverage do
16
+ rm_f "coverage"
17
+ rm_f "coverage.data"
18
+ rcov = "rcov --aggregate coverage.data"
19
+ system("#{rcov} --html test/*test.rb")
20
+ end
21
+
22
+
23
+ require 'jeweler'
24
+ Jeweler::Tasks.new do |gem|
25
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
26
+ gem.name = "vmsim"
27
+ gem.executables << 'vmsim'
28
+ gem.executables << 'vmdeploy'
29
+ gem.license = "MIT"
30
+ gem.summary = %Q{Vending machine simulator - part of the vendingmachine tdd bdd case}
31
+ gem.description = %Q{Vending machine; this simulator starts a vendingmachine ui which behaviour can be programmed with control code}
32
+ gem.email = "info@qwan.it"
33
+ gem.authors = ["Rob Westgeest, Marc Evers, Willem van den Ende"]
34
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
35
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
36
+ #gem.add_runtime_dependency 'gtk2', '>= 1.0.3'
37
+ gem.add_development_dependency 'mocha', '> 0.9.8'
38
+ end
39
+ Jeweler::RubygemsDotOrgTasks.new
40
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.3
data/bin/vmdeploy ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__),'..','lib')
3
+
4
+ require 'deploy'
5
+
6
+ def usage(errormessage = "")
7
+ puts errormessage
8
+ puts "usage: "
9
+ puts " vmdeploy <archive> [executable]"
10
+ puts ""
11
+ puts " archive : the archive to deploy"
12
+ puts " executable: the executable to execute after deployment - default : 'vmsim'"
13
+ puts ""
14
+ exit 1
15
+ end
16
+
17
+ archive = ARGV[0]
18
+ executable = ARGV[1] || 'vmsim'
19
+ usage unless archive
20
+ usage("archive #{archive} does not exist") unless File.exists?(archive)
21
+
22
+ Deploy::Deployer.new(executable).deploy ARGV[0]
23
+
data/bin/vmsim ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
4
+ require 'fileutils'
5
+ require 'hardware_adapter'
6
+ require 'hardware'
7
+ require 'gui'
8
+
9
+ simulator = Hardware::Simulator.new
10
+ simulator.assemble_hardware
11
+
12
+ simulator_gui = Gui::SimulatorGui.new(simulator)
13
+ simulator_gui.show
14
+
15
+ simulator.on_boot do
16
+ simulator.drawer(0).fill(Hardware::Can.cola, 2, 0)
17
+ simulator.drawer(1).fill(Hardware::Can.fanta, 2, 0)
18
+ simulator.drawer(2).fill(Hardware::Can.sprite, 2, 0)
19
+ simulator.drawer(3).fill(Hardware::Can.sisi, 2, 0)
20
+
21
+ simulator.cash_register.fill(Hardware::Coin.two_euro, 3, 0)
22
+ simulator.cash_register.fill(Hardware::Coin.one_euro, 3, 0)
23
+ simulator.cash_register.fill(Hardware::Coin.fifty_cents, 3, 0)
24
+ end
25
+
26
+ FileUtils.cd(File.expand_path('~/.vendingmachine/control')) do
27
+ $: << 'lib'
28
+ load 'main.rb'
29
+ Control.main if defined?(Control.main)
30
+ end
31
+
32
+ simulator.boot
33
+
34
+ while !simulator_gui.stopped? do
35
+ Gtk.main_iteration while Gtk.events_pending?
36
+ end
37
+ puts "vending machine going home drunk"
@@ -0,0 +1,32 @@
1
+ actuators
2
+ :cash_drop_100
3
+ :cash_drop_200
4
+ :cash_drop_50
5
+ :display_show, <line>, <message>
6
+ :drawer_drop_can_0
7
+ :drawer_drop_can_1
8
+ :drawer_drop_can_2
9
+ :drawer_drop_can_3
10
+
11
+
12
+ sensors
13
+ :bin_entry
14
+ :bin_fetch_all
15
+ :button_press_0
16
+ :button_press_1
17
+ :button_press_2
18
+ :button_press_3
19
+ :cash_fill_100
20
+ :cash_fill_200
21
+ :cash_fill_50
22
+ :cash_insert_100
23
+ :cash_insert_200
24
+ :cash_insert_50
25
+ :drawer_drop_can_0
26
+ :drawer_drop_can_1
27
+ :drawer_drop_can_2
28
+ :drawer_drop_can_3
29
+ :drawer_fill_can_0
30
+ :drawer_fill_can_1
31
+ :drawer_fill_can_2
32
+ :drawer_fill_can_3
data/doc/handout.odt ADDED
Binary file
data/doc/machine.jpg ADDED
Binary file
data/doc/todo.txt ADDED
@@ -0,0 +1,25 @@
1
+ - vmsim start - required in ~/.vendingmachine/control/main.rb
2
+ - zut in gem
3
+ - gem voor
4
+ - api voor display (show (line, message))
5
+ Devices.display_show(line, message)
6
+ Devices.on_button_pressed(0) { }
7
+
8
+ Devices.get_display().show(line, message)
9
+ Actuators(:display).fire(:show, line, message)
10
+ - api voor buttons (list, on_pres(0))
11
+ VM.get_button(0).on_press { }
12
+ Sensors.on(
13
+ - api voor drawers (on_pres(0), drop(0))
14
+ VM.get_drawer(0).drop()
15
+ - api voor bin (on_entry, on_fetch_all)
16
+ - api voor cashregister (on_fill(100), on_insert(100), drop(100))
17
+
18
+ ============== DONEDONEDONE ====================
19
+ - vmdeploy exe
20
+ - vmdeploy
21
+ - stopt vmsim als running
22
+ -
23
+ - start vmsim
24
+
25
+
@@ -0,0 +1,61 @@
1
+ module Adapter
2
+ class NoActuatorBlockGiven < Exception
3
+ end
4
+ class DuplicateActuatorException < Exception
5
+ end
6
+ class NoSuchActuatorException < Exception
7
+ end
8
+
9
+ class AsynchronousFirer
10
+ attr_reader :block
11
+ protected :block
12
+
13
+ def initialize(&block)
14
+ @block = block
15
+ end
16
+
17
+ def fire(*params)
18
+ Thread.start { block.call(*params) }
19
+ end
20
+ end
21
+
22
+ class SynchronousFirer < AsynchronousFirer
23
+ def fire(*params)
24
+ block.call *params
25
+ end
26
+ end
27
+
28
+
29
+ class ActuatorCollection
30
+ class Null
31
+ def create_actuator_for(actuator_name, &block)
32
+ end
33
+ def fire(actuator_name)
34
+ end
35
+ end
36
+
37
+ def self.null
38
+ Null.new
39
+ end
40
+
41
+ def initialize(firer_class = AsynchronousFirer)
42
+ @actuators = {}
43
+ @firer_class = firer_class
44
+ end
45
+
46
+ def list
47
+ @actuators.entries.collect {|e| e[0].to_s}.sort.collect{|e| e.to_sym}
48
+ end
49
+
50
+ def create_actuator_for(actuator_name, &block)
51
+ raise NoActuatorBlockGiven.new("must supply a code block for an actuator - none given") unless block_given?
52
+ raise DuplicateActuatorException.new("actuator :#{actuator_name} already exists") if @actuators.has_key?(actuator_name)
53
+ @actuators[actuator_name.to_sym] = @firer_class.new(&block)
54
+ end
55
+
56
+ def fire(actuator_name, *params)
57
+ raise NoSuchActuatorException.new("actuator :#{actuator_name} does not exist") unless @actuators.has_key?(actuator_name)
58
+ @actuators[actuator_name.to_sym].fire(*params)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,57 @@
1
+ module Adapter
2
+ class Sensor
3
+ def fire
4
+ @on_fire_block.call if @on_fire_block
5
+ end
6
+ def on_fire_do(&block)
7
+ @on_fire_block = block
8
+ end
9
+ def remove_fire_listener
10
+ @on_fire_block = nil
11
+ end
12
+ end
13
+
14
+ class NoSuchSensorException < Exception
15
+ end
16
+
17
+ class SensorCollection
18
+ class Null
19
+ def on(sensor)
20
+ end
21
+ def create_sensor_for(event)
22
+ end
23
+ def fire(sensor)
24
+ end
25
+ end
26
+
27
+ def initialize
28
+ @sensors = {}
29
+ @sensors.default = Sensor.new
30
+ end
31
+
32
+ def self.null
33
+ Null.new
34
+ end
35
+
36
+ def list
37
+ @sensors.entries.collect { |e| e[0].to_s }.sort.collect{ |e| e.to_sym }
38
+ end
39
+
40
+ def on(sensor, &block)
41
+ raise NoSuchSensorException.new("sensor :#{sensor} does not exist") unless @sensors.has_key?(sensor.to_sym)
42
+ @sensors[sensor.to_sym].on_fire_do(&block)
43
+ end
44
+
45
+ def create_sensor_for(event)
46
+ @sensors[event.to_sym] = Sensor.new
47
+ end
48
+
49
+ def fire(sensor)
50
+ @sensors[sensor.to_sym].fire
51
+ end
52
+
53
+ def remove_fire_listeners
54
+ @sensors.collect {|name, sensor| sensor.remove_fire_listener }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ require 'tmpdir'
2
+ module ArchiveBuilder
3
+
4
+ def an_archive(filename, work_dir = Dir.tmpdir, &block)
5
+ archive = ArchiveBuilder.new(filename, work_dir)
6
+ archive.in_work_dir(&block) if block_given?
7
+ return archive.build.file_path
8
+ end
9
+
10
+ require 'fileutils'
11
+ class ArchiveBuilder
12
+ include FileUtils
13
+ def initialize(filename, work_dir = Dir.tmpdir)
14
+ @filename = filename
15
+ @work_dir = File.join(work_dir, 'archive_builder')
16
+ end
17
+
18
+ def a_file(filename)
19
+ FileBuilder.new(filename)
20
+ end
21
+
22
+ def in_work_dir(&block)
23
+ mk_work_dir
24
+ cd(@work_dir) do
25
+ instance_eval(&block)
26
+ end
27
+ end
28
+
29
+ def build
30
+ cd(@work_dir) do
31
+ system "zip -r #{@filename} * >> /dev/null"
32
+ end
33
+ return self
34
+ end
35
+
36
+ def file_path
37
+ File.join(@work_dir, @filename)
38
+ end
39
+ private
40
+ def mk_work_dir
41
+ rm_r(@work_dir) if File.exists?(@work_dir) && @work_dir =~ /^\/tmp/
42
+ mkdir_p(@work_dir) unless File.exists?(@work_dir)
43
+ end
44
+ end
45
+
46
+ class FileBuilder
47
+ include FileUtils
48
+ attr_reader :filename
49
+ def initialize(filename)
50
+ @filename = filename
51
+ end
52
+
53
+ def with(content)
54
+ create_dirs
55
+ File.open(filename, "w+") { |f| f.puts content }
56
+ return self
57
+ end
58
+ def create_dirs
59
+ dirname = File.dirname(filename)
60
+ mkdir_p dirname unless File.exists?(dirname)
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,21 @@
1
+ require "vmlog"
2
+ require 'devices'
3
+
4
+ include VMLog
5
+
6
+ module Control
7
+ def self.main
8
+ # here, you should load your vending machine control software
9
+
10
+ # as an example of using the actuators and sensors (see doc/actuators_and_sensors)
11
+ Devices.display_show(0, 'Choose')
12
+ Devices.display_show(1, 'Please...')
13
+
14
+ Devices.on_button_pressed(0) do
15
+ log "cola pressed"
16
+
17
+ Devices.display_show(0, 'Cola')
18
+ end
19
+ end
20
+ end
21
+
data/lib/control.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'control/vending_machine'
2
+ require 'control/main'
3
+
@@ -0,0 +1,96 @@
1
+
2
+ require 'fileutils'
3
+ module Deploy
4
+ def self.deploy(file)
5
+ Deployer.new(file).deploy
6
+ end
7
+ class Deployer
8
+ include FileUtils
9
+ DEPLOYED_DIR = File.expand_path("~/.vendingmachine/control")
10
+ PIDFILE = File.expand_path("~/.vendingmachine/pid")
11
+
12
+ attr_reader :executable, :current_pid
13
+
14
+ def initialize(executable)
15
+ @executable = executable
16
+ @log = ""
17
+ $: << DEPLOYED_DIR unless $:.include?(DEPLOYED_DIR)
18
+ end
19
+
20
+ def run_log
21
+ @log
22
+ end
23
+
24
+ def deploy(file_path)
25
+ create_deployment_environment
26
+ read_current_pid
27
+ kill_running_process
28
+ install_deployed_code(file_path)
29
+ start_deployed_code
30
+ end
31
+
32
+ def kill
33
+ read_current_pid
34
+ kill_running_process
35
+ end
36
+
37
+ def wait_for
38
+ @running_thread.join if @running_thread
39
+ self
40
+ end
41
+
42
+ private
43
+ def start_deployed_code
44
+ @running_thread = Thread.start do
45
+ IO.popen(executable) do |io|
46
+ save_current_pid io.pid
47
+ while !io.eof? do
48
+ @log << io.gets
49
+ end
50
+ end
51
+ clear_current_pid
52
+ end
53
+ end
54
+
55
+ def install_deployed_code(file_path)
56
+ rm_r Dir.glob(File.join(DEPLOYED_DIR, '*'))
57
+ file_path = File.expand_path(file_path)
58
+ cd(DEPLOYED_DIR) do
59
+ sh "unzip #{file_path}"
60
+ end
61
+ end
62
+
63
+ def kill_running_process
64
+ begin
65
+ p current_pid
66
+ Process.kill("SIGKILL", current_pid) if current_pid
67
+ rescue Errno::ESRCH => e
68
+ # thats fine
69
+ end
70
+ end
71
+
72
+ def save_current_pid(pid)
73
+ @current_pid = pid
74
+ File.open(PIDFILE, 'w+') { |f| f.write(@current_pid.to_s) }
75
+ end
76
+
77
+ def read_current_pid
78
+ @current_pid = File.read(PIDFILE).to_i if File.exists?(PIDFILE)
79
+ end
80
+
81
+ def clear_current_pid
82
+ @current_pid = nil
83
+ rm PIDFILE if File.exists?(PIDFILE)
84
+ end
85
+
86
+ def create_deployment_environment
87
+ mkdir_p DEPLOYED_DIR unless File.exists?(DEPLOYED_DIR)
88
+ end
89
+
90
+ def sh(command)
91
+ system "#{command} >> /dev/null"
92
+ end
93
+
94
+ end
95
+ end
96
+
data/lib/deploy.rb ADDED
@@ -0,0 +1 @@
1
+ require 'deploy/deployer'
@@ -0,0 +1,73 @@
1
+ require 'hardware_adapter'
2
+ module Devices
3
+
4
+ def self.method_missing(method_sym, *arguments, &block)
5
+ devices = Devices.new
6
+ devices.send(method_sym, *arguments, &block)
7
+ end
8
+
9
+ class Devices
10
+ def initialize(actuators = Adapter.actuators, sensors = Adapter.sensors)
11
+ @actuators = actuators
12
+ @sensors = sensors
13
+ end
14
+
15
+ # display a a message on the specified line on the display
16
+ # the line is a 0 based index in the available lines on the display
17
+ def display_show(line, message)
18
+ actuators.fire(:display_show, line, message)
19
+ end
20
+
21
+ # drops a coin in the bin where:
22
+ # coin is 50 100 or 200 cts
23
+ def cash_drop_coin(coin)
24
+ actuators.fire(:"cash_drop_#{coin}")
25
+ end
26
+
27
+ # register a handler on filling coin of a specific
28
+ # kind in the cash register
29
+ def on_cash_filled(coin, &block)
30
+ sensors.on(:"cash_fill_#{coin}", &block)
31
+ end
32
+
33
+ # register a handler on inserting a specifica coin
34
+ # in the cash register as part of paying
35
+ def on_cash_inserted(coin, &block)
36
+ sensors.on(:"cash_insert_#{coin}", &block)
37
+ end
38
+
39
+ # register a handler on getting something being dropped in the bin
40
+ def on_bin_entry(&block)
41
+ sensors.on(:bin_entry, &block)
42
+ end
43
+
44
+ # register a handler on sbdy getting stuff from the bin
45
+ def on_bin_fetch_all(&block)
46
+ sensors.on(:bin_fetch_all, &block)
47
+ end
48
+
49
+ # register a handleer on sbgy pressing a specific button
50
+ # where button can be one of 0 1 2 or three
51
+ def on_button_pressed(button, &block)
52
+ sensors.on(:"button_press_#{button}", &block)
53
+ end
54
+
55
+ # drops a can from a specified drawer
56
+ def drawer_drop_can(drawer)
57
+ actuators.fire(:"drawer_drop_can_#{drawer}")
58
+ end
59
+
60
+ # register a handler on a drawer dropping a can
61
+ def on_drawer_dropped(drawer, &block)
62
+ sensors.on(:"drawer_drop_can_#{drawer}", &block)
63
+ end
64
+
65
+ # register a handler on a drawer being filled with a can
66
+ def on_drawer_filled(drawer, &block)
67
+ sensors.on(:"drawer_fill_can_#{drawer}", &block)
68
+ end
69
+
70
+ private
71
+ attr_reader :actuators, :sensors
72
+ end
73
+ end
data/lib/devices.rb ADDED
@@ -0,0 +1 @@
1
+ require 'devices/devices'