vmsim 0.2.3

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