virtualbox 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.yardopts CHANGED
@@ -1,3 +1,4 @@
1
1
  -
2
2
  docs/GettingStarted.md
3
+ docs/WhatsNew.md
3
4
  TODO
data/TODO CHANGED
@@ -5,6 +5,5 @@ But they will definitely all make it for version 1.0.
5
5
 
6
6
  * Creating a VM from scratch (non-import)
7
7
  * Parsing bridged IFs
8
- * Shared folders
9
8
  * Snapshots
10
9
  * Getting/Setting guest properties
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/docs/WhatsNew.md ADDED
@@ -0,0 +1,41 @@
1
+ # What's New in 0.3.x?
2
+
3
+ ## Shared Folders
4
+
5
+ Shared folders are a great feature of VirtualBox which allows the host system
6
+ to share data with guest systems easily using the native filesystem. Attaching,
7
+ modifying, and removing these shared folders are now supported. A quick example
8
+ below:
9
+
10
+ vm = VirtualBox::VM.find("FooVM")
11
+ folder = VirtualBox::SharedFolder.new
12
+ folder.name = "hosthome"
13
+ folder.hostpath = "/home/username"
14
+ vm.shared_folders << folder
15
+ vm.save
16
+
17
+ For full documentation on this new feature, read about them at
18
+ {VirtualBox::SharedFolder}.
19
+
20
+ ## Validations
21
+
22
+ Many of the models for the virtualbox library now come complete with data
23
+ validations. These validations are performed within the library itself prior to
24
+ calling the virtualbox commands. They work very much the same was as ActiveRecord
25
+ validations:
26
+
27
+ sf = VirtualBox::SharedFolder.new(hash_of_values)
28
+ if !sf.valid?
29
+ puts "#{sf.errors.length} errors with the folder"
30
+ else
31
+ sf.save
32
+ end
33
+
34
+ In addition to `valid?` there is `errors` which returns a hash of all the errors,
35
+ including errors on relationships. There is also the `validate` method which
36
+ runs the validations, but you really shouldn't have the need to call that directly.
37
+
38
+ All validations are run automatically on `save`, which will return `false` if
39
+ they fail. If you choose to raise errors on the save, a `ValidationFailedException`
40
+ will be raised (in contrast to a `CommandFailedException`, which serves its own
41
+ role).
data/lib/virtualbox.rb CHANGED
@@ -8,5 +8,6 @@ require 'virtualbox/attached_device'
8
8
  require 'virtualbox/dvd'
9
9
  require 'virtualbox/hard_drive'
10
10
  require 'virtualbox/nic'
11
+ require 'virtualbox/shared_folder'
11
12
  require 'virtualbox/storage_controller'
12
13
  require 'virtualbox/vm'
@@ -1,6 +1,7 @@
1
1
  require 'virtualbox/abstract_model/attributable'
2
2
  require 'virtualbox/abstract_model/dirty'
3
3
  require 'virtualbox/abstract_model/relatable'
4
+ require 'virtualbox/abstract_model/validatable'
4
5
 
5
6
  module VirtualBox
6
7
  # AbstractModel is the base class used for most of virtualbox's classes.
@@ -11,6 +12,7 @@ module VirtualBox
11
12
  include Attributable
12
13
  include Dirty
13
14
  include Relatable
15
+ include Validatable
14
16
 
15
17
  # Returns a boolean denoting if the record is new or existing. This
16
18
  # method is provided for subclasses to use to differentiate between
@@ -36,6 +38,35 @@ module VirtualBox
36
38
  @new_record = false
37
39
  end
38
40
 
41
+ # Returns the errors for a model.
42
+ def errors
43
+ error_hash = super
44
+
45
+ self.class.relationships.each do |name, options|
46
+ next unless options && options[:klass].respond_to?(:errors_for_relationship)
47
+ relationship_errors = options[:klass].errors_for_relationship(self, relationship_data[name])
48
+
49
+ error_hash.merge!({ :foos => relationship_errors }) if relationship_errors.length > 0
50
+ end
51
+
52
+ error_hash
53
+ end
54
+
55
+ # Validates the model and relationships.
56
+ def validate(*args)
57
+ # First clear all previous errors
58
+ clear_errors
59
+
60
+ # Then do the validations
61
+ failed = false
62
+ self.class.relationships.each do |name, options|
63
+ next unless options && options[:klass].respond_to?(:validate_relationship)
64
+ failed = true if !options[:klass].validate_relationship(self, relationship_data[name], *args)
65
+ end
66
+
67
+ return !failed
68
+ end
69
+
39
70
  # Saves the model attributes and relationships.
40
71
  #
41
72
  # The method can be passed any arbitrary arguments, which are
@@ -0,0 +1,44 @@
1
+ module VirtualBox
2
+ class AbstractModel
3
+ # Provides validation methods for a class. Unlike ActiveRecord,
4
+ # validations are instance-level rather than class-level.
5
+ module Validatable
6
+ def errors
7
+ @errors ||= {}
8
+ end
9
+
10
+ def add_error(field, error)
11
+ errors[field] ||= []
12
+ errors[field].push(error)
13
+ end
14
+
15
+ def clear_errors
16
+ @errors = {}
17
+ end
18
+
19
+ def valid?
20
+ validate
21
+ errors.empty?
22
+ end
23
+
24
+ # Subclasses should override this method.
25
+ def validate
26
+ true
27
+ end
28
+
29
+ def validates_presence_of(field)
30
+ if field.is_a?(Array)
31
+ field.map { |v| validates_presence_of(v) }.all? { |v| v == true }
32
+ else
33
+ value = send(field)
34
+ if value.nil? || value == ""
35
+ add_error(field, "must not be blank.")
36
+ return false
37
+ else
38
+ return true
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -140,16 +140,28 @@ module VirtualBox
140
140
  end
141
141
  end
142
142
 
143
+ # Validates an attached device.
144
+ def validate
145
+ super
146
+
147
+ validates_presence_of :parent
148
+ validates_presence_of :image
149
+ validates_presence_of :port
150
+ end
151
+
143
152
  # Saves or creates an attached device.
144
153
  #
145
154
  # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
146
155
  # will be raised if the command failed.
147
156
  # @return [Boolean] True if command was successful, false otherwise.
148
- def save(raise_errors=false)
149
- raise Exceptions::NoParentException.new if parent.nil?
150
- raise Exceptions::InvalidObjectException.new("Image must be set") if image.nil?
157
+ def save(raise_errors=false)
151
158
  return true unless changed?
152
159
 
160
+ if !valid?
161
+ raise Exceptions::ValidationFailedException.new(errors) if raise_errors
162
+ return false
163
+ end
164
+
153
165
  # If the port changed, we have to destroy the old one, then create
154
166
  # a new one
155
167
  destroy({:port => port_was}, raise_errors) if port_changed? && !port_was.nil?
@@ -5,9 +5,8 @@ module VirtualBox
5
5
  class Exception < ::Exception; end
6
6
 
7
7
  class CommandFailedException < Exception; end
8
- class InvalidObjectException < Exception; end
9
8
  class InvalidRelationshipObjectException < Exception; end
10
9
  class NonSettableRelationshipException < Exception; end
11
- class NoParentException < Exception; end
10
+ class ValidationFailedException < Exception; end
12
11
  end
13
12
  end
@@ -138,6 +138,14 @@ module VirtualBox
138
138
  "hdd"
139
139
  end
140
140
 
141
+ # Validates a hard drive.
142
+ def validate
143
+ super
144
+
145
+ validates_presence_of :format
146
+ validates_presence_of :size
147
+ end
148
+
141
149
  # Creates a new hard drive.
142
150
  #
143
151
  # **This method should NEVER be called. Call {#save} instead.**
@@ -146,6 +154,11 @@ module VirtualBox
146
154
  # will be raised if the command failed.
147
155
  # @return [Boolean] True if command was successful, false otherwise.
148
156
  def create(raise_errors=false)
157
+ if !valid?
158
+ raise Exceptions::ValidationFailedException.new(errors) if raise_errors
159
+ return false
160
+ end
161
+
149
162
  raw = Command.vboxmanage("createhd --filename #{location} --size #{size} --format #{read_attribute(:format)} --remember")
150
163
  return nil unless raw =~ /UUID: (.+?)$/
151
164
 
@@ -0,0 +1,243 @@
1
+ module VirtualBox
2
+ # Represents a shared folder in VirtualBox. In VirtualBox, shared folders are a
3
+ # method for basically "symlinking" a folder on the guest system to a folder which
4
+ # exists on the host system. This allows for sharing of files across the virtual
5
+ # machine.
6
+ #
7
+ # **Note:** Whenever modifying shared folders on a VM, the changes won't take
8
+ # effect until a _cold reboot_ occurs. This means actually closing the virtual
9
+ # machine _completely_, then restarting it. You can't just hit "Start > Restart"
10
+ # or do a `sudo reboot`. It doesn't work that way!
11
+ #
12
+ # # Getting Shared Folders
13
+ #
14
+ # All shared folders are attached to a {VM} object, by definition. Therefore, to
15
+ # get a list of the shared folders, first {VM.find find} the VM you need, then
16
+ # use the `shared_folders` relationship to access an array of the shared folders.
17
+ # With this array, you can create, modify, update, and delete the shared folders
18
+ # for that virtual machine.
19
+ #
20
+ # # Creating a Shared Folder
21
+ #
22
+ # **This whole section will assume you already looked up a {VM} and assigned it to
23
+ # a local variable named `vm`.**
24
+ #
25
+ # With a VM found, creating a shared folder is just a few lines of code:
26
+ #
27
+ # folder = VirtualBox::SharedFolder.new
28
+ # folder.name = "desktop-images"
29
+ # folder.hostpath = File.expand_path("~/Desktop/images")
30
+ # vm.shared_folders << folder
31
+ # folder.save # Or you can call vm.save, which works too!
32
+ #
33
+ # # Modifying an Existing Shared Folder
34
+ #
35
+ # **This whole section will assume you already looked up a {VM} and assigned it to
36
+ # a local variable named `vm`.**
37
+ #
38
+ # Nothing tricky here: You treat existing shared folder objects just as if they
39
+ # were new ones. Assign a new name and/or a new path, then save.
40
+ #
41
+ # folder = vm.shared_folders.first
42
+ # folder.name = "rufus"
43
+ # folder.save # Or vm.save
44
+ #
45
+ # **Note**: The VirtualBox-saavy will know that VirtualBox doesn't actually
46
+ # expose a way to edit shared folders. Under the hood, the virtualbox ruby
47
+ # library is actually deleting the old shared folder, then creating a new
48
+ # one with the new details. This shouldn't affect the way anything works for
49
+ # the VM itself.
50
+ #
51
+ # # Deleting a Shared Folder
52
+ #
53
+ # **This whole section will assume you already looked up a {VM} and assigned it to
54
+ # a local variable named `vm`.**
55
+ #
56
+ # folder = vm.shared_folder.first
57
+ # folder.destroy
58
+ #
59
+ # Poof! It'll be gone. This is usually the place where I warn you about this
60
+ # being non-reversable, but since no _data_ was actually destroyed, this is
61
+ # not too risky. You could always just recreate the shared folder with the
62
+ # same name and path and it'll be like nothing happened.
63
+ #
64
+ # # Attributes and Relationships
65
+ #
66
+ # Properties of the model are exposed using standard ruby instance
67
+ # methods which are generated on the fly. Because of this, they are not listed
68
+ # below as available instance methods.
69
+ #
70
+ # These attributes can be accessed and modified via standard ruby-style
71
+ # `instance.attribute` and `instance.attribute=` methods. The attributes are
72
+ # listed below.
73
+ #
74
+ # Relationships are also accessed like attributes but can't be set. Instead,
75
+ # they are typically references to other objects such as an {AttachedDevice} which
76
+ # in turn have their own attributes which can be modified.
77
+ #
78
+ # ## Attributes
79
+ #
80
+ # This is copied directly from the class header, but lists all available
81
+ # attributes. If you don't understand what this means, read {Attributable}.
82
+ #
83
+ # attribute :parent, :readonly => :readonly
84
+ # attribute :name
85
+ # attribute :hostpath
86
+ #
87
+ class SharedFolder < AbstractModel
88
+ attribute :parent, :readonly => :readonly
89
+ attribute :name, :populate_key => "SharedFolderNameMachineMapping"
90
+ attribute :hostpath, :populate_key => "SharedFolderPathMachineMapping"
91
+
92
+ class <<self
93
+ # Populates the shared folder relationship for anything which is related to it.
94
+ #
95
+ # **This method typically won't be used except internally.**
96
+ #
97
+ # @return [Array<SharedFolder>]
98
+ def populate_relationship(caller, data)
99
+ relation = []
100
+
101
+ counter = 1
102
+ loop do
103
+ break unless data["SharedFolderNameMachineMapping#{counter}".downcase.to_sym]
104
+
105
+ folder = new(counter, caller, data)
106
+ relation.push(folder)
107
+ counter += 1
108
+ end
109
+
110
+ relation
111
+ end
112
+
113
+ # Saves the relationship. This simply calls {#save} on every
114
+ # member of the relationship.
115
+ #
116
+ # **This method typically won't be used except internally.**
117
+ def save_relationship(caller, data)
118
+ # Just call save on each folder with the VM
119
+ data.each do |sf|
120
+ sf.save
121
+ end
122
+ end
123
+ end
124
+
125
+ # @overload initialize(data={})
126
+ # Creates a new SharedFolder which is a new record. This
127
+ # should be attached to a VM and saved.
128
+ # @param [Hash] data (optional) A hash which contains initial attribute
129
+ # values for the SharedFolder.
130
+ # @overload initialize(index, caller, data)
131
+ # Creates an SharedFolder for a relationship. **This should
132
+ # never be called except internally.**
133
+ # @param [Integer] index Index of the shared folder
134
+ # @param [Object] caller The parent
135
+ # @param [Hash] data A hash of data which must be used
136
+ # to extract the relationship data.
137
+ def initialize(*args)
138
+ super()
139
+
140
+ if args.length == 3
141
+ initialize_for_relationship(*args)
142
+ elsif args.length == 1
143
+ initialize_for_data(*args)
144
+ elsif args.length == 0
145
+ return
146
+ else
147
+ raise NoMethodError.new
148
+ end
149
+ end
150
+
151
+ # Initializes the record for use in a relationship. This
152
+ # is automatically called by {#initialize} if it has three
153
+ # parameters.
154
+ #
155
+ # **This method typically won't be used except internally.**
156
+ def initialize_for_relationship(index, caller, data)
157
+ # Setup the index specific attributes
158
+ populate_data = {}
159
+ self.class.attributes.each do |name, options|
160
+ key = options[:populate_key] || name
161
+ value = data["#{key}#{index}".downcase.to_sym]
162
+ populate_data[key] = value
163
+ end
164
+
165
+ populate_attributes(populate_data.merge({
166
+ :parent => caller
167
+ }))
168
+ end
169
+
170
+ # Initializes a record with initial data but keeping it a "new
171
+ # record." This is called automatically if {#initialize} is given
172
+ # only a single parameter. View {#initialize} for documentation.
173
+ def initialize_for_data(data)
174
+ self.class.attributes.each do |name, options|
175
+ data[options[:populate_key]] = data[name]
176
+ end
177
+
178
+ populate_attributes(data)
179
+ new_record!
180
+ end
181
+
182
+ # Validates a shared folder.
183
+ def validate
184
+ super
185
+
186
+ validates_presence_of :parent
187
+ validates_presence_of :name
188
+ validates_presence_of :hostpath
189
+ end
190
+
191
+ # Saves or creates a shared folder.
192
+ #
193
+ # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
194
+ # will be raised if the command failed.
195
+ # @return [Boolean] True if command was successful, false otherwise.
196
+ def save(raise_errors=false)
197
+ return true unless changed?
198
+
199
+ if !valid?
200
+ raise Exceptions::ValidationFailedException.new(errors) if raise_errors
201
+ return false
202
+ end
203
+
204
+ # If this isn't a new record, we destroy it first
205
+ destroy(raise_errors) if !new_record?
206
+
207
+ Command.vboxmanage("sharedfolder add #{Command.shell_escape(parent.name)} --name #{Command.shell_escape(name)} --hostpath #{Command.shell_escape(hostpath)}")
208
+ existing_record!
209
+ clear_dirty!
210
+
211
+ true
212
+ rescue Exceptions::CommandFailedException
213
+ raise if raise_errors
214
+ false
215
+ end
216
+
217
+ # Relationship callback when added to a collection. This is automatically
218
+ # called by any relationship collection when this object is added.
219
+ def added_to_relationship(parent)
220
+ write_attribute(:parent, parent)
221
+ end
222
+
223
+ # Destroys the shared folder. This doesn't actually delete the folder
224
+ # from the host system. Instead, it simply removes the mapping to the
225
+ # virtual machine, meaning it will no longer be possible to mount it
226
+ # from within the virtual machine.
227
+ #
228
+ # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
229
+ # will be raised if the command failed.
230
+ # @return [Boolean] True if command was successful, false otherwise.
231
+ def destroy(raise_errors=false)
232
+ # If the name changed, we have to be sure to use the previous
233
+ # one.
234
+ name_value = name_changed? ? name_was : name
235
+
236
+ Command.vboxmanage("sharedfolder remove #{Command.shell_escape(parent.name)} --name #{Command.shell_escape(name_value)}")
237
+ true
238
+ rescue Exceptions::CommandFailedException
239
+ raise if raise_errors
240
+ false
241
+ end
242
+ end
243
+ end
data/lib/virtualbox/vm.rb CHANGED
@@ -74,6 +74,7 @@ module VirtualBox
74
74
  #
75
75
  # relationship :nics, Nic
76
76
  # relationship :storage_controllers, StorageController, :dependent => :destroy
77
+ # relationship :shared_folders, SharedFolder
77
78
  #
78
79
  class VM < AbstractModel
79
80
  attribute :uuid, :readonly => true
@@ -104,6 +105,7 @@ module VirtualBox
104
105
  attribute :state, :populate_key => :vmstate, :readonly => true
105
106
  relationship :nics, Nic
106
107
  relationship :storage_controllers, StorageController, :dependent => :destroy
108
+ relationship :shared_folders, SharedFolder
107
109
 
108
110
  class <<self
109
111
  # Returns an array of all available VMs.
@@ -0,0 +1,125 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class ValidatableTest < Test::Unit::TestCase
4
+ class ValidatableModel
5
+ include VirtualBox::AbstractModel::Validatable
6
+
7
+ attr_accessor :foos
8
+ attr_accessor :bars
9
+ end
10
+
11
+ context "errors" do
12
+ setup do
13
+ @model = ValidatableModel.new
14
+ end
15
+
16
+ should "have no errors by default" do
17
+ assert @model.errors.empty?
18
+ end
19
+
20
+ should "be able to add errors" do
21
+ @model.add_error(:foo, "is blank")
22
+ assert !@model.errors.empty?
23
+ assert !@model.errors[:foo].nil?
24
+ assert_equal "is blank", @model.errors[:foo].first
25
+ end
26
+
27
+ should "be able to add multiple errors" do
28
+ @model.add_error(:foo, "foo")
29
+ @model.add_error(:foo, "bar")
30
+ assert !@model.errors.empty?
31
+ assert !@model.errors[:foo].nil?
32
+ assert_equal 2, @model.errors[:foo].length
33
+ end
34
+
35
+ should "be able to clear errors" do
36
+ @model.add_error(:foo, "foo")
37
+ assert !@model.errors.empty?
38
+ @model.clear_errors
39
+ assert @model.errors.empty?
40
+ end
41
+ end
42
+
43
+ context "validity" do
44
+ setup do
45
+ @model = ValidatableModel.new
46
+ end
47
+
48
+ should "call validate on valid?" do
49
+ @model.expects(:validate)
50
+ assert @model.valid?
51
+ end
52
+
53
+ should "be valid if there are no errors" do
54
+ assert @model.valid?
55
+ end
56
+
57
+ should "be invalid if there are any errors" do
58
+ @model.add_error(:foo, "foo")
59
+ assert !@model.valid?
60
+ end
61
+
62
+ should "have a validate method by default which returns true" do
63
+ assert @model.validate
64
+ end
65
+ end
66
+
67
+ context "specific validations" do
68
+ setup do
69
+ @model = ValidatableModel.new
70
+ end
71
+
72
+ context "validates_presence_of" do
73
+ setup do
74
+ @model.foos = "foo"
75
+ @model.bars = "bar"
76
+ end
77
+
78
+ should "not add an error if not blank" do
79
+ @model.validates_presence_of(:foos)
80
+ assert @model.valid?
81
+ end
82
+
83
+ should "add an error if blank field" do
84
+ @model.foos = ""
85
+ @model.validates_presence_of(:foos)
86
+ assert !@model.valid?
87
+ end
88
+
89
+ should "add an error for a nil field" do
90
+ @model.foos = nil
91
+ @model.validates_presence_of(:foos)
92
+ assert !@model.valid?
93
+ end
94
+
95
+ should "validate multiple fields" do
96
+ @model.bars = nil
97
+ @model.validates_presence_of([:foos, :bars])
98
+
99
+ assert !@model.valid?
100
+ assert @model.errors[:bars]
101
+ end
102
+
103
+ should "return false on invalid" do
104
+ @model.bars = nil
105
+ assert !@model.validates_presence_of(:bars)
106
+ end
107
+
108
+ should "return true on valid" do
109
+ @model.bars = "foo"
110
+ assert @model.validates_presence_of(:bars)
111
+ end
112
+
113
+ should "return false if any are invalid on multiple fields" do
114
+ @model.bars = nil
115
+ assert !@model.validates_presence_of([:foos, :bars])
116
+ end
117
+
118
+ should "return true if all fields are valid" do
119
+ @model.foos = "foo"
120
+ @model.bars = "bar"
121
+ assert @model.validates_presence_of([:foos, :bars])
122
+ end
123
+ end
124
+ end
125
+ end
@@ -5,6 +5,9 @@ class AbstractModelTest < Test::Unit::TestCase
5
5
  def self.set_relationship(caller, old_value, new_value)
6
6
  new_value
7
7
  end
8
+
9
+ def self.validate_relationship(caller, data)
10
+ end
8
11
  end
9
12
 
10
13
  class Bar; end
@@ -16,6 +19,52 @@ class AbstractModelTest < Test::Unit::TestCase
16
19
  relationship :bars, Bar, :dependent => :destroy
17
20
  end
18
21
 
22
+ context "validation" do
23
+ setup do
24
+ @model = FakeModel.new
25
+ end
26
+
27
+ should "clear all previous errors" do
28
+ @model.expects(:clear_errors).once
29
+ @model.validate
30
+ end
31
+
32
+ should "call validate_relationship on each relationship class" do
33
+ Foo.expects(:validate_relationship).once.with(@model, nil)
34
+ @model.validate
35
+ end
36
+
37
+ should "forward arguments to validate_relationship" do
38
+ Foo.expects(:validate_relationship).once.with(@model, nil, "HELLO")
39
+ @model.validate("HELLO")
40
+ end
41
+
42
+ should "succeed if all relationships succeeded" do
43
+ Foo.expects(:validate_relationship).returns(true)
44
+ assert @model.validate
45
+ end
46
+
47
+ should "fail if one relationship failed to validate" do
48
+ Foo.expects(:validate_relationship).returns(true)
49
+ Bar.expects(:validate_relationship).returns(false)
50
+ assert !@model.validate
51
+ end
52
+
53
+ context "errors" do
54
+ should "return the errors of the relationships, as well as the model itself" do
55
+ @model.foo = nil
56
+ assert !@model.validate
57
+
58
+ @model.validates_presence_of(:foo)
59
+ Foo.expects(:errors_for_relationship).with(@model, nil).returns("BAD")
60
+ errors = @model.errors
61
+ assert errors.has_key?(:foos)
62
+ assert_equal "BAD", errors[:foos]
63
+ assert errors.has_key?(:foo)
64
+ end
65
+ end
66
+ end
67
+
19
68
  context "new/existing records" do
20
69
  setup do
21
70
  @model = FakeModel.new
@@ -19,6 +19,34 @@ class AttachedDeviceTest < Test::Unit::TestCase
19
19
  VirtualBox::Command.stubs(:execute).returns('')
20
20
  end
21
21
 
22
+ context "validations" do
23
+ setup do
24
+ @ad = VirtualBox::AttachedDevice.new
25
+ @ad.image = VirtualBox::DVD.empty_drive
26
+ @ad.port = 7
27
+ @ad.added_to_relationship(@caller)
28
+ end
29
+
30
+ should "be valid with all fields" do
31
+ assert @ad.valid?
32
+ end
33
+
34
+ should "be invalid with no image" do
35
+ @ad.image = nil
36
+ assert !@ad.valid?
37
+ end
38
+
39
+ should "be invalid with no port" do
40
+ @ad.port = nil
41
+ assert !@ad.valid?
42
+ end
43
+
44
+ should "be invalid if not in a relationship" do
45
+ @ad.write_attribute(:parent, nil)
46
+ assert !@ad.valid?
47
+ end
48
+ end
49
+
22
50
  context "medium" do
23
51
  setup do
24
52
  @ad = VirtualBox::AttachedDevice.new
@@ -62,6 +90,12 @@ class AttachedDeviceTest < Test::Unit::TestCase
62
90
  @value.save
63
91
  end
64
92
 
93
+ should "return false and not call vboxmanage if invalid" do
94
+ VirtualBox::Command.expects(:vboxmanage).never
95
+ @value.expects(:valid?).returns(false)
96
+ assert !@value.save
97
+ end
98
+
65
99
  should "not call destroy if the port didn't change" do
66
100
  @value.expects(:destroy).never
67
101
  assert !@value.port_changed?
@@ -90,9 +124,16 @@ class AttachedDeviceTest < Test::Unit::TestCase
90
124
  @ad.port = 3
91
125
  end
92
126
 
93
- should "raise a NoParentException if it wasn't added to a relationship" do
94
- assert_raises(VirtualBox::Exceptions::NoParentException) {
95
- @ad.save
127
+ should "return false and not call vboxmanage if invalid" do
128
+ VirtualBox::Command.expects(:vboxmanage).never
129
+ @ad.expects(:valid?).returns(false)
130
+ assert !@ad.save
131
+ end
132
+
133
+ should "raise a ValidationFailedException if invalid and raise_errors is true" do
134
+ @ad.expects(:valid?).returns(false)
135
+ assert_raises(VirtualBox::Exceptions::ValidationFailedException) {
136
+ @ad.save(true)
96
137
  }
97
138
  end
98
139
 
@@ -106,13 +147,6 @@ class AttachedDeviceTest < Test::Unit::TestCase
106
147
  @ad.expects(:destroy).never
107
148
  assert @ad.save
108
149
  end
109
-
110
- should "not raise an InvalidObjectException if no image is set" do
111
- @ad.image = nil
112
- assert_raises(VirtualBox::Exceptions::InvalidObjectException) {
113
- @ad.save
114
- }
115
- end
116
150
 
117
151
  should "call the proper vboxcommand" do
118
152
  VirtualBox::Command.expects(:vboxmanage).with("storageattach #{@vm.name} --storagectl #{VirtualBox::Command.shell_escape(@caller.name)} --port #{@ad.port} --device 0 --type #{@image.image_type} --medium #{@ad.medium}")
@@ -23,6 +23,29 @@ raw
23
23
  VirtualBox::Command.stubs(:vboxmanage).with("showhdinfo #{@name}").returns(@find_raw)
24
24
  end
25
25
 
26
+ context "validations" do
27
+ setup do
28
+ @hd = VirtualBox::HardDrive.new
29
+ @hd.size = 2000
30
+ end
31
+
32
+ should "be valid with a size and format" do
33
+ assert @hd.valid?
34
+ end
35
+
36
+ should "be invalid if size is nil" do
37
+ @hd.size = nil
38
+ assert !@hd.valid?
39
+ end
40
+
41
+ should "clear validations when rechecking" do
42
+ @hd.size = nil
43
+ assert !@hd.valid?
44
+ @hd.size = 700
45
+ assert @hd.valid?
46
+ end
47
+ end
48
+
26
49
  context "destroying a hard drive" do
27
50
  setup do
28
51
  @hd = VirtualBox::HardDrive.find(@name)
@@ -136,6 +159,19 @@ raw
136
159
  @hd.save(true)
137
160
  }
138
161
  end
162
+
163
+ should "not run if invalid" do
164
+ @hd.expects(:valid?).returns(false)
165
+ VirtualBox::Command.expects(:vboxmanage).never
166
+ assert !@hd.save
167
+ end
168
+
169
+ should "raise a ValidationFailedException if invalid and raise_errors is true" do
170
+ @hd.expects(:valid?).returns(false)
171
+ assert_raises(VirtualBox::Exceptions::ValidationFailedException) {
172
+ @hd.save(true)
173
+ }
174
+ end
139
175
  end
140
176
 
141
177
  context "finding a single hard drive" do
@@ -0,0 +1,231 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class SharedFolderTest < Test::Unit::TestCase
4
+ setup do
5
+ @data = {
6
+ :sharedfoldernamemachinemapping1 => "foofolder",
7
+ :sharedfolderpathmachinemapping1 => "/foo",
8
+ :sharedfoldernamemachinemapping2 => "barfolder",
9
+ :sharedfolderpathmachinemapping2 => "/bar"
10
+ }
11
+
12
+ @caller = mock("caller")
13
+ @caller.stubs(:name).returns("foo")
14
+
15
+ VirtualBox::Command.stubs(:execute)
16
+ end
17
+
18
+ context "validations" do
19
+ setup do
20
+ @sf = VirtualBox::SharedFolder.new
21
+ @sf.name = "foo"
22
+ @sf.hostpath = "bar"
23
+ @sf.added_to_relationship(@caller)
24
+ end
25
+
26
+ should "be valid with all fields" do
27
+ assert @sf.valid?
28
+ end
29
+
30
+ should "be invalid with no name" do
31
+ @sf.name = nil
32
+ assert !@sf.valid?
33
+ end
34
+
35
+ should "be invalid with no hostpath" do
36
+ @sf.hostpath = nil
37
+ assert !@sf.valid?
38
+ end
39
+
40
+ should "be invalid if not in a relationship" do
41
+ @sf.write_attribute(:parent, nil)
42
+ assert !@sf.valid?
43
+ end
44
+ end
45
+
46
+ context "saving an existing shared folder" do
47
+ setup do
48
+ @value = VirtualBox::SharedFolder.populate_relationship(@caller, @data)
49
+ @value = @value[0]
50
+ @value.name = "different"
51
+ assert @value.changed?
52
+ end
53
+
54
+ should "first destroy the shared folder then recreate it" do
55
+ seq = sequence("create_seq")
56
+ @value.expects(:destroy).in_sequence(seq)
57
+ VirtualBox::Command.expects(:vboxmanage).in_sequence(seq)
58
+ assert @value.save
59
+ end
60
+
61
+ should "call destroy with raise errors if set" do
62
+ @value.expects(:destroy).with(true).once
63
+ assert @value.save(true)
64
+ end
65
+ end
66
+
67
+ context "creating a new shared folder" do
68
+ setup do
69
+ @sf = VirtualBox::SharedFolder.new
70
+ @sf.name = "foo"
71
+ @sf.hostpath = "bar"
72
+ end
73
+
74
+ should "return false and not call vboxmanage if invalid" do
75
+ VirtualBox::Command.expects(:vboxmanage).never
76
+ @sf.expects(:valid?).returns(false)
77
+ assert !@sf.save
78
+ end
79
+
80
+ should "raise a ValidationFailedException if invalid and raise_errors is true" do
81
+ @sf.expects(:valid?).returns(false)
82
+ assert_raises(VirtualBox::Exceptions::ValidationFailedException) {
83
+ @sf.save(true)
84
+ }
85
+ end
86
+
87
+ context "has a parent" do
88
+ setup do
89
+ @sf.added_to_relationship(@caller)
90
+ VirtualBox::Command.stubs(:vboxmanage)
91
+ end
92
+
93
+ should "not call destroy since its a new record" do
94
+ @sf.expects(:destroy).never
95
+ assert @sf.save
96
+ end
97
+
98
+ should "call the proper vboxcommand" do
99
+ VirtualBox::Command.expects(:vboxmanage).with("sharedfolder add #{@caller.name} --name #{@sf.name} --hostpath #{@sf.hostpath}")
100
+ assert @sf.save
101
+ end
102
+
103
+ should "return false if the command failed" do
104
+ VirtualBox::Command.stubs(:vboxmanage).raises(VirtualBox::Exceptions::CommandFailedException)
105
+ assert !@sf.save
106
+ end
107
+
108
+ should "return true if the command was a success" do
109
+ assert @sf.save
110
+ end
111
+
112
+ should "raise an exception if true sent to save and error occured" do
113
+ VirtualBox::Command.stubs(:vboxmanage).raises(VirtualBox::Exceptions::CommandFailedException)
114
+ assert_raises(VirtualBox::Exceptions::CommandFailedException) {
115
+ @sf.save(true)
116
+ }
117
+ end
118
+
119
+ should "not be a new record after saving" do
120
+ assert @sf.new_record?
121
+ assert @sf.save
122
+ assert !@sf.new_record?
123
+ end
124
+
125
+ should "not be changed after saving" do
126
+ assert @sf.changed?
127
+ assert @sf.save
128
+ assert !@sf.changed?
129
+ end
130
+ end
131
+ end
132
+
133
+ context "constructor" do
134
+ should "call initialize_for_relationship if 3 args are given" do
135
+ VirtualBox::SharedFolder.any_instance.expects(:initialize_for_relationship).with(1,2,3).once
136
+ VirtualBox::SharedFolder.new(1,2,3)
137
+ end
138
+
139
+ should "raise a NoMethodError if anything other than 0,1,or 3 arguments" do
140
+ 2.upto(9) do |i|
141
+ next if i == 3
142
+ args = Array.new(i, "A")
143
+
144
+ assert_raises(NoMethodError) {
145
+ VirtualBox::SharedFolder.new(*args)
146
+ }
147
+ end
148
+ end
149
+
150
+ should "populate from a hash if one argument is given" do
151
+ VirtualBox::SharedFolder.any_instance.expects(:initialize_for_data).with("HI").once
152
+ VirtualBox::SharedFolder.new("HI")
153
+ end
154
+
155
+ context "initializing from data" do
156
+ setup do
157
+ @sf = VirtualBox::SharedFolder.new({:name => "foo", :hostpath => "bar"})
158
+ end
159
+
160
+ should "allow the use of :name and :hostpath in the hash" do
161
+ assert_equal "foo", @sf.name
162
+ assert_equal "bar", @sf.hostpath
163
+ end
164
+
165
+ should "keep the record new" do
166
+ assert @sf.new_record?
167
+ end
168
+ end
169
+ end
170
+
171
+ context "destroying" do
172
+ setup do
173
+ @value = VirtualBox::SharedFolder.populate_relationship(@caller, @data)
174
+ @value = @value[0]
175
+ end
176
+
177
+ should "call the proper command" do
178
+ VirtualBox::Command.expects(:vboxmanage).with("sharedfolder remove #{@caller.name} --name #{@value.name}").once
179
+ assert @value.destroy
180
+ end
181
+
182
+ should "shell escape VM name and storage controller name" do
183
+ shell_seq = sequence("shell_seq")
184
+ VirtualBox::Command.expects(:shell_escape).with(@caller.name).in_sequence(shell_seq)
185
+ VirtualBox::Command.expects(:shell_escape).with(@value.name).in_sequence(shell_seq)
186
+ VirtualBox::Command.expects(:vboxmanage).in_sequence(shell_seq)
187
+ assert @value.destroy
188
+ end
189
+
190
+ should "return false if destroy failed" do
191
+ VirtualBox::Command.stubs(:vboxmanage).raises(VirtualBox::Exceptions::CommandFailedException)
192
+ assert !@value.destroy
193
+ end
194
+
195
+ should "raise an exception if destroy failed and an error occured" do
196
+ VirtualBox::Command.stubs(:vboxmanage).raises(VirtualBox::Exceptions::CommandFailedException)
197
+ assert_raises(VirtualBox::Exceptions::CommandFailedException) {
198
+ @value.destroy(true)
199
+ }
200
+ end
201
+
202
+ should "use the old name if it was changed" do
203
+ @value.name = "DIFFERENT"
204
+ shell_seq = sequence("shell_seq")
205
+ VirtualBox::Command.expects(:shell_escape).with(@caller.name).in_sequence(shell_seq)
206
+ VirtualBox::Command.expects(:shell_escape).with(@value.name_was).in_sequence(shell_seq)
207
+ VirtualBox::Command.expects(:vboxmanage).in_sequence(shell_seq)
208
+ assert @value.destroy
209
+ end
210
+ end
211
+
212
+ context "populating relationships" do
213
+ setup do
214
+ @value = VirtualBox::SharedFolder.populate_relationship(@caller, @data)
215
+ end
216
+
217
+ should "create the correct amount of objects" do
218
+ assert_equal 2, @value.length
219
+ end
220
+
221
+ should "parse the proper data" do
222
+ value = @value[0]
223
+ assert_equal "foofolder", value.name
224
+ assert_equal "/foo", value.hostpath
225
+
226
+ value = @value[1]
227
+ assert_equal "barfolder", value.name
228
+ assert_equal "/bar", value.hostpath
229
+ end
230
+ end
231
+ end
@@ -71,7 +71,11 @@ uart2="off"
71
71
  audio="none"
72
72
  clipboard="bidirectional"
73
73
  vrdp="off"
74
- usb="off"
74
+ usb="off"
75
+ SharedFolderNameMachineMapping1="mysharedfolder"
76
+ SharedFolderPathMachineMapping1="/virtualbox"
77
+ SharedFolderNameMachineMapping2="otherfolder"
78
+ SharedFolderPathMachineMapping2="/virtualbox/lib"
75
79
  showvminfo
76
80
 
77
81
  @name = "foo"
@@ -354,6 +358,7 @@ raw
354
358
  should "save the relationships as well" do
355
359
  VirtualBox::Nic.expects(:save_relationship).once
356
360
  VirtualBox::StorageController.expects(:save_relationship).once
361
+ VirtualBox::SharedFolder.expects(:save_relationship).once
357
362
  assert @vm.save
358
363
  end
359
364
  end
@@ -391,6 +396,12 @@ raw
391
396
  assert @vm.storage_controllers.is_a?(Array)
392
397
  assert_equal 2, @vm.storage_controllers.length
393
398
  end
399
+
400
+ should "properly load shared folder relationship" do
401
+ assert @vm.shared_folders
402
+ assert @vm.shared_folders.is_a?(Array)
403
+ assert_equal 2, @vm.shared_folders.length
404
+ end
394
405
  end
395
406
 
396
407
  context "parsing the showvminfo output" do
data/virtualbox.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{virtualbox}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Mitchell Hashimoto"]
12
- s.date = %q{2010-01-26}
12
+ s.date = %q{2010-01-28}
13
13
  s.description = %q{Create and modify virtual machines in VirtualBox using pure ruby.}
14
14
  s.email = %q{mitchell.hashimoto@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -24,11 +24,13 @@ Gem::Specification.new do |s|
24
24
  "TODO",
25
25
  "VERSION",
26
26
  "docs/GettingStarted.md",
27
+ "docs/WhatsNew.md",
27
28
  "lib/virtualbox.rb",
28
29
  "lib/virtualbox/abstract_model.rb",
29
30
  "lib/virtualbox/abstract_model/attributable.rb",
30
31
  "lib/virtualbox/abstract_model/dirty.rb",
31
32
  "lib/virtualbox/abstract_model/relatable.rb",
33
+ "lib/virtualbox/abstract_model/validatable.rb",
32
34
  "lib/virtualbox/attached_device.rb",
33
35
  "lib/virtualbox/command.rb",
34
36
  "lib/virtualbox/dvd.rb",
@@ -38,12 +40,14 @@ Gem::Specification.new do |s|
38
40
  "lib/virtualbox/image.rb",
39
41
  "lib/virtualbox/nic.rb",
40
42
  "lib/virtualbox/proxies/collection.rb",
43
+ "lib/virtualbox/shared_folder.rb",
41
44
  "lib/virtualbox/storage_controller.rb",
42
45
  "lib/virtualbox/vm.rb",
43
46
  "test/test_helper.rb",
44
47
  "test/virtualbox/abstract_model/attributable_test.rb",
45
48
  "test/virtualbox/abstract_model/dirty_test.rb",
46
49
  "test/virtualbox/abstract_model/relatable_test.rb",
50
+ "test/virtualbox/abstract_model/validatable_test.rb",
47
51
  "test/virtualbox/abstract_model_test.rb",
48
52
  "test/virtualbox/attached_device_test.rb",
49
53
  "test/virtualbox/command_test.rb",
@@ -53,6 +57,7 @@ Gem::Specification.new do |s|
53
57
  "test/virtualbox/image_test.rb",
54
58
  "test/virtualbox/nic_test.rb",
55
59
  "test/virtualbox/proxies/collection_test.rb",
60
+ "test/virtualbox/shared_folder_test.rb",
56
61
  "test/virtualbox/storage_controller_test.rb",
57
62
  "test/virtualbox/vm_test.rb",
58
63
  "virtualbox.gemspec"
@@ -67,6 +72,7 @@ Gem::Specification.new do |s|
67
72
  "test/virtualbox/abstract_model/attributable_test.rb",
68
73
  "test/virtualbox/abstract_model/dirty_test.rb",
69
74
  "test/virtualbox/abstract_model/relatable_test.rb",
75
+ "test/virtualbox/abstract_model/validatable_test.rb",
70
76
  "test/virtualbox/abstract_model_test.rb",
71
77
  "test/virtualbox/attached_device_test.rb",
72
78
  "test/virtualbox/command_test.rb",
@@ -76,6 +82,7 @@ Gem::Specification.new do |s|
76
82
  "test/virtualbox/image_test.rb",
77
83
  "test/virtualbox/nic_test.rb",
78
84
  "test/virtualbox/proxies/collection_test.rb",
85
+ "test/virtualbox/shared_folder_test.rb",
79
86
  "test/virtualbox/storage_controller_test.rb",
80
87
  "test/virtualbox/vm_test.rb"
81
88
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virtualbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitchell Hashimoto
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-26 00:00:00 -08:00
12
+ date: 2010-01-28 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,11 +30,13 @@ files:
30
30
  - TODO
31
31
  - VERSION
32
32
  - docs/GettingStarted.md
33
+ - docs/WhatsNew.md
33
34
  - lib/virtualbox.rb
34
35
  - lib/virtualbox/abstract_model.rb
35
36
  - lib/virtualbox/abstract_model/attributable.rb
36
37
  - lib/virtualbox/abstract_model/dirty.rb
37
38
  - lib/virtualbox/abstract_model/relatable.rb
39
+ - lib/virtualbox/abstract_model/validatable.rb
38
40
  - lib/virtualbox/attached_device.rb
39
41
  - lib/virtualbox/command.rb
40
42
  - lib/virtualbox/dvd.rb
@@ -44,12 +46,14 @@ files:
44
46
  - lib/virtualbox/image.rb
45
47
  - lib/virtualbox/nic.rb
46
48
  - lib/virtualbox/proxies/collection.rb
49
+ - lib/virtualbox/shared_folder.rb
47
50
  - lib/virtualbox/storage_controller.rb
48
51
  - lib/virtualbox/vm.rb
49
52
  - test/test_helper.rb
50
53
  - test/virtualbox/abstract_model/attributable_test.rb
51
54
  - test/virtualbox/abstract_model/dirty_test.rb
52
55
  - test/virtualbox/abstract_model/relatable_test.rb
56
+ - test/virtualbox/abstract_model/validatable_test.rb
53
57
  - test/virtualbox/abstract_model_test.rb
54
58
  - test/virtualbox/attached_device_test.rb
55
59
  - test/virtualbox/command_test.rb
@@ -59,6 +63,7 @@ files:
59
63
  - test/virtualbox/image_test.rb
60
64
  - test/virtualbox/nic_test.rb
61
65
  - test/virtualbox/proxies/collection_test.rb
66
+ - test/virtualbox/shared_folder_test.rb
62
67
  - test/virtualbox/storage_controller_test.rb
63
68
  - test/virtualbox/vm_test.rb
64
69
  - virtualbox.gemspec
@@ -95,6 +100,7 @@ test_files:
95
100
  - test/virtualbox/abstract_model/attributable_test.rb
96
101
  - test/virtualbox/abstract_model/dirty_test.rb
97
102
  - test/virtualbox/abstract_model/relatable_test.rb
103
+ - test/virtualbox/abstract_model/validatable_test.rb
98
104
  - test/virtualbox/abstract_model_test.rb
99
105
  - test/virtualbox/attached_device_test.rb
100
106
  - test/virtualbox/command_test.rb
@@ -104,5 +110,6 @@ test_files:
104
110
  - test/virtualbox/image_test.rb
105
111
  - test/virtualbox/nic_test.rb
106
112
  - test/virtualbox/proxies/collection_test.rb
113
+ - test/virtualbox/shared_folder_test.rb
107
114
  - test/virtualbox/storage_controller_test.rb
108
115
  - test/virtualbox/vm_test.rb