virtualbox 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +11 -0
- data/Rakefile +32 -0
- data/Readme.md +75 -0
- data/TODO +13 -0
- data/VERSION +1 -0
- data/lib/virtualbox.rb +11 -0
- data/lib/virtualbox/abstract_model.rb +95 -0
- data/lib/virtualbox/abstract_model/attributable.rb +193 -0
- data/lib/virtualbox/abstract_model/dirty.rb +164 -0
- data/lib/virtualbox/abstract_model/relatable.rb +163 -0
- data/lib/virtualbox/attached_device.rb +104 -0
- data/lib/virtualbox/command.rb +54 -0
- data/lib/virtualbox/dvd.rb +31 -0
- data/lib/virtualbox/errors.rb +7 -0
- data/lib/virtualbox/ext/subclass_listing.rb +24 -0
- data/lib/virtualbox/hard_drive.rb +169 -0
- data/lib/virtualbox/image.rb +94 -0
- data/lib/virtualbox/nic.rb +150 -0
- data/lib/virtualbox/storage_controller.rb +122 -0
- data/lib/virtualbox/vm.rb +287 -0
- data/test/test_helper.rb +25 -0
- data/test/virtualbox/abstract_model/attributable_test.rb +150 -0
- data/test/virtualbox/abstract_model/dirty_test.rb +66 -0
- data/test/virtualbox/abstract_model/relatable_test.rb +141 -0
- data/test/virtualbox/abstract_model_test.rb +146 -0
- data/test/virtualbox/attached_device_test.rb +92 -0
- data/test/virtualbox/command_test.rb +30 -0
- data/test/virtualbox/dvd_test.rb +58 -0
- data/test/virtualbox/ext/subclass_listing_test.rb +25 -0
- data/test/virtualbox/hard_drive_test.rb +161 -0
- data/test/virtualbox/image_test.rb +113 -0
- data/test/virtualbox/nic_test.rb +119 -0
- data/test/virtualbox/storage_controller_test.rb +79 -0
- data/test/virtualbox/vm_test.rb +313 -0
- metadata +103 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a single storage controller which can be attached to a
|
3
|
+
# virtual machine.
|
4
|
+
#
|
5
|
+
# **Currently, storage controllers can not be created from scratch.
|
6
|
+
# Therefore, the only way to use this model is through a relationship
|
7
|
+
# of a {VM} object.**
|
8
|
+
#
|
9
|
+
# # Attributes and Relationships
|
10
|
+
#
|
11
|
+
# Properties of the storage controller are exposed using standard ruby instance
|
12
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
13
|
+
# below as available instance methods.
|
14
|
+
#
|
15
|
+
# These attributes can be accessed and modified via standard ruby-style
|
16
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
17
|
+
# listed below.
|
18
|
+
#
|
19
|
+
# Relationships are also accessed like attributes but can't be set. Instead,
|
20
|
+
# they are typically references to other objects such as an {AttachedDevice} which
|
21
|
+
# in turn have their own attributes which can be modified.
|
22
|
+
#
|
23
|
+
# ## Attributes
|
24
|
+
#
|
25
|
+
# This is copied directly from the class header, but lists all available
|
26
|
+
# attributes. If you don't understand what this means, read {Attributable}.
|
27
|
+
#
|
28
|
+
# attribute :parent, :readonly => true
|
29
|
+
# attribute :name
|
30
|
+
# attribute :type
|
31
|
+
# attribute :max_ports, :populate_key => :maxportcount
|
32
|
+
# attribute :ports, :populate_key => :portcount
|
33
|
+
#
|
34
|
+
# ## Relationships
|
35
|
+
#
|
36
|
+
# In addition to the basic attributes, a virtual machine is related
|
37
|
+
# to other things. The relationships are listed below. If you don't
|
38
|
+
# understand this, read {Relatable}.
|
39
|
+
#
|
40
|
+
# relationship :devices, AttachedDevice, :dependent => :destroy
|
41
|
+
#
|
42
|
+
class StorageController < AbstractModel
|
43
|
+
attribute :parent, :readonly => true
|
44
|
+
attribute :name
|
45
|
+
attribute :type
|
46
|
+
attribute :max_ports, :populate_key => :maxportcount
|
47
|
+
attribute :ports, :populate_key => :portcount
|
48
|
+
relationship :devices, AttachedDevice, :dependent => :destroy
|
49
|
+
|
50
|
+
class <<self
|
51
|
+
# Populates a relationship with another model.
|
52
|
+
#
|
53
|
+
# **This method typically won't be used except internally.**
|
54
|
+
#
|
55
|
+
# @return [Array<StorageController>]
|
56
|
+
def populate_relationship(caller, data)
|
57
|
+
relation = []
|
58
|
+
|
59
|
+
counter = 0
|
60
|
+
loop do
|
61
|
+
break unless data["storagecontrollername#{counter}".to_sym]
|
62
|
+
nic = new(counter, caller, data)
|
63
|
+
relation.push(nic)
|
64
|
+
counter += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
relation
|
68
|
+
end
|
69
|
+
|
70
|
+
# Destroys a relationship with another model.
|
71
|
+
#
|
72
|
+
# **This method typically won't be used except internally.**
|
73
|
+
def destroy_relationship(caller, data, *args)
|
74
|
+
data.each { |v| v.destroy(*args) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Since storage controllers still can't be created from scratch,
|
79
|
+
# this method shouldn't be called. Instead, storage controllers
|
80
|
+
# can be retrieved through relationships of other models such
|
81
|
+
# as {VM}.
|
82
|
+
def initialize(index, caller, data)
|
83
|
+
super()
|
84
|
+
|
85
|
+
@index = index
|
86
|
+
|
87
|
+
# Setup the index specific attributes
|
88
|
+
populate_data = {}
|
89
|
+
self.class.attributes.each do |name, options|
|
90
|
+
key = options[:populate_key] || name
|
91
|
+
value = data["storagecontroller#{key}#{index}".to_sym]
|
92
|
+
populate_data[key] = value
|
93
|
+
end
|
94
|
+
|
95
|
+
# Make sure to merge in device data so those relationships will be
|
96
|
+
# setup properly
|
97
|
+
populate_data.merge!(extract_devices(index, data))
|
98
|
+
|
99
|
+
populate_attributes(populate_data.merge({
|
100
|
+
:parent => caller
|
101
|
+
}))
|
102
|
+
end
|
103
|
+
|
104
|
+
# Extracts related devices for a storage controller.
|
105
|
+
#
|
106
|
+
# **This method typically won't be used except internally.**
|
107
|
+
#
|
108
|
+
# @return [Hash]
|
109
|
+
def extract_devices(index, data)
|
110
|
+
name = data["storagecontrollername#{index}".downcase.to_sym].downcase
|
111
|
+
|
112
|
+
device_data = {}
|
113
|
+
data.each do |k,v|
|
114
|
+
next unless k.to_s =~ /^#{name}-/
|
115
|
+
|
116
|
+
device_data[k] = v
|
117
|
+
end
|
118
|
+
|
119
|
+
device_data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
# Represents a single VirtualBox virtual machine. All attributes which are
|
3
|
+
# not read-only can be modified and saved.
|
4
|
+
#
|
5
|
+
# # Finding Virtual Machines
|
6
|
+
#
|
7
|
+
# Two methods are used to find virtual machines: {VM.all} and {VM.find}. Each
|
8
|
+
# return a {VM} object. An example is shown below:
|
9
|
+
#
|
10
|
+
# vm = VirtualBox::VM.find("MyWindowsXP")
|
11
|
+
# puts vm.name # => "MyWindowsXP"
|
12
|
+
#
|
13
|
+
# # Modifying Virtual Machines
|
14
|
+
#
|
15
|
+
# Virtual machines can be modified a lot like [ActiveRecord](http://ar.rubyonrails.org/)
|
16
|
+
# objects. This is best shown through example:
|
17
|
+
#
|
18
|
+
# vm = VirtualBox::VM.find("MyWindowsXP")
|
19
|
+
# vm.memory = 256
|
20
|
+
# vm.name = "WindowsXP"
|
21
|
+
# vm.save
|
22
|
+
#
|
23
|
+
# # Attributes and Relationships
|
24
|
+
#
|
25
|
+
# Properties of the virtual machine are exposed using standard ruby instance
|
26
|
+
# methods which are generated on the fly. Because of this, they are not listed
|
27
|
+
# below as available instance methods.
|
28
|
+
#
|
29
|
+
# These attributes can be accessed and modified via standard ruby-style
|
30
|
+
# `instance.attribute` and `instance.attribute=` methods. The attributes are
|
31
|
+
# listed below.
|
32
|
+
#
|
33
|
+
# Relationships are also accessed like attributes but can't be set. Instead,
|
34
|
+
# they are typically references to other objects such as a {HardDrive} which
|
35
|
+
# in turn have their own attributes which can be modified.
|
36
|
+
#
|
37
|
+
# ## Attributes
|
38
|
+
#
|
39
|
+
# This is copied directly from the class header, but lists all available
|
40
|
+
# attributes. If you don't understand what this means, read {Attributable}.
|
41
|
+
#
|
42
|
+
# attribute :uuid, :readonly => true
|
43
|
+
# attribute :name
|
44
|
+
# attribute :ostype
|
45
|
+
# attribute :memory
|
46
|
+
# attribute :vram
|
47
|
+
# attribute :acpi
|
48
|
+
# attribute :ioapic
|
49
|
+
# attribute :cpus
|
50
|
+
# attribute :synthcpu
|
51
|
+
# attribute :pae
|
52
|
+
# attribute :hwvirtex
|
53
|
+
# attribute :hwvirtexexcl
|
54
|
+
# attribute :nestedpaging
|
55
|
+
# attribute :vtxvpid
|
56
|
+
# attribute :accelerate3d
|
57
|
+
# attribute :biosbootmenu, :populate_key => :bootmenu
|
58
|
+
# attribute :boot1
|
59
|
+
# attribute :boot2
|
60
|
+
# attribute :boot3
|
61
|
+
# attribute :boot4
|
62
|
+
# attribute :clipboard
|
63
|
+
# attribute :monitorcount
|
64
|
+
# attribute :usb
|
65
|
+
# attribute :audio
|
66
|
+
# attribute :vrdp
|
67
|
+
# attribute :state, :populate_key => :vmstate, :readonly => true
|
68
|
+
#
|
69
|
+
# ## Relationships
|
70
|
+
#
|
71
|
+
# In addition to the basic attributes, a virtual machine is related
|
72
|
+
# to other things. The relationships are listed below. If you don't
|
73
|
+
# understand this, read {Relatable}.
|
74
|
+
#
|
75
|
+
# relationship :nics, Nic
|
76
|
+
# relationship :storage_controllers, StorageController, :dependent => :destroy
|
77
|
+
#
|
78
|
+
class VM < AbstractModel
|
79
|
+
attribute :uuid, :readonly => true
|
80
|
+
attribute :name
|
81
|
+
attribute :ostype
|
82
|
+
attribute :memory
|
83
|
+
attribute :vram
|
84
|
+
attribute :acpi
|
85
|
+
attribute :ioapic
|
86
|
+
attribute :cpus
|
87
|
+
attribute :synthcpu
|
88
|
+
attribute :pae
|
89
|
+
attribute :hwvirtex
|
90
|
+
attribute :hwvirtexexcl
|
91
|
+
attribute :nestedpaging
|
92
|
+
attribute :vtxvpid
|
93
|
+
attribute :accelerate3d
|
94
|
+
attribute :biosbootmenu, :populate_key => :bootmenu
|
95
|
+
attribute :boot1
|
96
|
+
attribute :boot2
|
97
|
+
attribute :boot3
|
98
|
+
attribute :boot4
|
99
|
+
attribute :clipboard
|
100
|
+
attribute :monitorcount
|
101
|
+
attribute :usb
|
102
|
+
attribute :audio
|
103
|
+
attribute :vrdp
|
104
|
+
attribute :state, :populate_key => :vmstate, :readonly => true
|
105
|
+
relationship :nics, Nic
|
106
|
+
relationship :storage_controllers, StorageController, :dependent => :destroy
|
107
|
+
|
108
|
+
class <<self
|
109
|
+
# Returns an array of all available VMs.
|
110
|
+
#
|
111
|
+
# @return [Array<VM>]
|
112
|
+
def all
|
113
|
+
raw = Command.vboxmanage("list vms")
|
114
|
+
parse_vm_list(raw)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Finds a VM by UUID or registered name and returns a
|
118
|
+
# new VM object. If the VM doesn't exist, will return `nil`.
|
119
|
+
#
|
120
|
+
# @return [VM]
|
121
|
+
def find(name)
|
122
|
+
new(raw_info(name))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Imports a VM, blocking the entire thread during this time.
|
126
|
+
# When finished, on success, will return the VM object. This
|
127
|
+
# VM object can be used to make any modifications necessary
|
128
|
+
# (RAM, cpus, etc.).
|
129
|
+
#
|
130
|
+
# @return [VM] The newly imported virtual machine
|
131
|
+
def import(source_path)
|
132
|
+
raw = Command.vboxmanage("import #{Command.shell_escape(source_path)}")
|
133
|
+
return nil unless raw
|
134
|
+
|
135
|
+
find(parse_vm_name(raw))
|
136
|
+
end
|
137
|
+
|
138
|
+
# Gets the non-machine-readable info for a given VM and returns
|
139
|
+
# it as a raw string.
|
140
|
+
#
|
141
|
+
# **This method typically won't be used except internally.**
|
142
|
+
#
|
143
|
+
# @return [String]
|
144
|
+
def human_info(name)
|
145
|
+
Command.vboxmanage("showvminfo #{name}")
|
146
|
+
end
|
147
|
+
|
148
|
+
# Gets the VM info (machine readable) for a given VM and returns it
|
149
|
+
# as a hash.
|
150
|
+
#
|
151
|
+
# @return [Hash] Parsed VM info.
|
152
|
+
def raw_info(name)
|
153
|
+
raw = Command.vboxmanage("showvminfo #{name} --machinereadable")
|
154
|
+
parse_vm_info(raw)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Parses the machine-readable format outputted by VBoxManage showvminfo
|
158
|
+
# into a hash. Ignores lines which don't match the format.
|
159
|
+
def parse_vm_info(raw)
|
160
|
+
parsed = {}
|
161
|
+
raw.lines.each do |line|
|
162
|
+
# Some lines aren't configuration, we just ignore them
|
163
|
+
next unless line =~ /^"?(.+?)"?="?(.+?)"?$/
|
164
|
+
parsed[$1.downcase.to_sym] = $2.strip
|
165
|
+
end
|
166
|
+
|
167
|
+
parsed
|
168
|
+
end
|
169
|
+
|
170
|
+
# Parses the list of VMs returned by the "list vms" command used
|
171
|
+
# in {VM.all}.
|
172
|
+
#
|
173
|
+
# **This method typically won't be used except internally.**
|
174
|
+
#
|
175
|
+
# @return [Array] Array of virtual machines.
|
176
|
+
def parse_vm_list(raw)
|
177
|
+
results = []
|
178
|
+
raw.lines.each do |line|
|
179
|
+
next unless line =~ /^"(.+?)"\s+\{(.+?)\}$/
|
180
|
+
results.push(find($1.to_s))
|
181
|
+
end
|
182
|
+
|
183
|
+
results
|
184
|
+
end
|
185
|
+
|
186
|
+
# Parses the vm name from the import results.
|
187
|
+
#
|
188
|
+
# **This method typically won't be used except internally.**
|
189
|
+
#
|
190
|
+
# @return [String] Parsed VM name
|
191
|
+
def parse_vm_name(raw)
|
192
|
+
return nil unless raw =~ /VM name "(.+?)"/
|
193
|
+
$1.to_s
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Creates a new instance of a virtual machine.
|
198
|
+
#
|
199
|
+
# **Currently can NOT be used to create a NEW virtual machine**.
|
200
|
+
# Support for creating new virtual machines will be added shortly.
|
201
|
+
# For now, this is only used by {VM.find} and {VM.all} to
|
202
|
+
# initialize the VMs.
|
203
|
+
def initialize(data)
|
204
|
+
super()
|
205
|
+
|
206
|
+
populate_attributes(data)
|
207
|
+
@original_name = data[:name]
|
208
|
+
end
|
209
|
+
|
210
|
+
# State of the virtual machine. Returns the state of the virtual
|
211
|
+
# machine. This state will represent the state that was assigned
|
212
|
+
# when the VM was found unless `reload` is set to `true`.
|
213
|
+
#
|
214
|
+
# @param [Boolean] reload If true, will reload the state to current
|
215
|
+
# value.
|
216
|
+
# @return [String] Virtual machine state.
|
217
|
+
def state(reload=false)
|
218
|
+
if reload
|
219
|
+
info = self.class.raw_info(@original_name)
|
220
|
+
write_attribute(:state, info[:vmstate])
|
221
|
+
end
|
222
|
+
|
223
|
+
read_attribute(:state)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Saves the virtual machine if modified. This method saves any modified
|
227
|
+
# attributes of the virtual machine. If any related attributes were saved
|
228
|
+
# as well (such as storage controllers), those will be saved, too.
|
229
|
+
def save
|
230
|
+
# Make sure we save the new name first if that was changed, or
|
231
|
+
# we'll get some inconsistencies later
|
232
|
+
if name_changed?
|
233
|
+
save_attribute(:name, name)
|
234
|
+
@original_name = name
|
235
|
+
end
|
236
|
+
|
237
|
+
super
|
238
|
+
end
|
239
|
+
|
240
|
+
# Saves a single attribute of the virtual machine. This should **not**
|
241
|
+
# be called except interally. Instead, you're probably looking for {#save}.
|
242
|
+
#
|
243
|
+
# **This method typically won't be used except internally.**
|
244
|
+
def save_attribute(key, value)
|
245
|
+
Command.vboxmanage("modifyvm #{@original_name} --#{key} #{Command.shell_escape(value.to_s)}")
|
246
|
+
super
|
247
|
+
end
|
248
|
+
|
249
|
+
# Starts the virtual machine. The virtual machine can be started in a
|
250
|
+
# variety of modes:
|
251
|
+
#
|
252
|
+
# * **gui** -- The VirtualBox GUI will open with the screen of the VM.
|
253
|
+
# * **headless** -- The VM will run in the background. No GUI will be
|
254
|
+
# present at all.
|
255
|
+
#
|
256
|
+
# All modes will start their processes and return almost immediately.
|
257
|
+
# Both the GUI and headless mode will not block the ruby process.
|
258
|
+
def start(mode=:gui)
|
259
|
+
Command.vboxmanage("startvm #{@original_name} --type #{mode}")
|
260
|
+
end
|
261
|
+
|
262
|
+
# Stops the VM by directly calling "poweroff." Immediately halts the
|
263
|
+
# virtual machine without saving state. This could result in a loss
|
264
|
+
# of data.
|
265
|
+
def stop
|
266
|
+
Command.vboxmanage("controlvm #{@original_name} poweroff")
|
267
|
+
end
|
268
|
+
|
269
|
+
# Destroys the virtual machine. This method also removes all attached
|
270
|
+
# media (required by VirtualBox to destroy a VM). By default,
|
271
|
+
# this **will not** destroy attached hard drives, but will if given
|
272
|
+
# the `destroy_image` option.
|
273
|
+
#
|
274
|
+
# @overload destroy(opts = {})
|
275
|
+
# Passes options to the destroy method.
|
276
|
+
# @option opts [Boolean] :destroy_image (false) If true, will
|
277
|
+
# also destroy all attached images such as hard drives, disk
|
278
|
+
# images, etc.
|
279
|
+
def destroy(*args)
|
280
|
+
# Call super first to destroy relationships, necessary before
|
281
|
+
# unregistering a VM
|
282
|
+
super
|
283
|
+
|
284
|
+
Command.vboxmanage("unregistervm #{@original_name} --delete")
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment')
|
3
|
+
rescue LoadError
|
4
|
+
puts <<-ENVERR
|
5
|
+
==================================================
|
6
|
+
ERROR: Gem environment file not found!
|
7
|
+
|
8
|
+
This gem uses bundler to handle gem dependencies. To setup the
|
9
|
+
test environment, please run `gem bundle test` If you don't
|
10
|
+
have bundler, you can install that with `gem install bundler`
|
11
|
+
==================================================
|
12
|
+
ENVERR
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
# ruby-debug, not necessary, but useful if we have it
|
17
|
+
begin
|
18
|
+
require 'ruby-debug'
|
19
|
+
rescue LoadError; end
|
20
|
+
|
21
|
+
require 'contest'
|
22
|
+
require 'mocha'
|
23
|
+
|
24
|
+
# The actual library
|
25
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'virtualbox')
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
|
2
|
+
|
3
|
+
class AttributableTest < Test::Unit::TestCase
|
4
|
+
class EmptyAttributeModel
|
5
|
+
include VirtualBox::AbstractModel::Attributable
|
6
|
+
end
|
7
|
+
|
8
|
+
class AttributeModel < EmptyAttributeModel
|
9
|
+
attribute :foo
|
10
|
+
attribute :bar
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
|
15
|
+
populate_attributes({
|
16
|
+
:foo => "foo",
|
17
|
+
:bar => "bar"
|
18
|
+
})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "subclasses" do
|
23
|
+
class SubModel < AttributeModel
|
24
|
+
attribute :baz
|
25
|
+
end
|
26
|
+
|
27
|
+
should "have foo bar and baz" do
|
28
|
+
attributes = SubModel.attributes
|
29
|
+
assert attributes.has_key?(:foo)
|
30
|
+
assert attributes.has_key?(:bar)
|
31
|
+
assert attributes.has_key?(:baz)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "attribute options" do
|
36
|
+
context "custom populate keys" do
|
37
|
+
class CustomPopulateModel < AttributeModel
|
38
|
+
attribute :foo, :populate_key => :foo_key
|
39
|
+
end
|
40
|
+
|
41
|
+
setup do
|
42
|
+
@model = CustomPopulateModel.new
|
43
|
+
end
|
44
|
+
|
45
|
+
should "use the populate key instead of the attribute name" do
|
46
|
+
@model.populate_attributes({
|
47
|
+
:foo => "not me!",
|
48
|
+
:foo_key => "bar"
|
49
|
+
})
|
50
|
+
|
51
|
+
assert_equal "bar", @model.foo
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "readonly attributes" do
|
56
|
+
class ReadonlyModel < AttributeModel
|
57
|
+
attribute :foo, :readonly => :readonly
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
super
|
61
|
+
populate_attributes({ :foo => "foo" })
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
setup do
|
66
|
+
@model = ReadonlyModel.new
|
67
|
+
end
|
68
|
+
|
69
|
+
should "be readonly" do
|
70
|
+
assert @model.readonly_attribute?(:foo)
|
71
|
+
end
|
72
|
+
|
73
|
+
should "allow reading" do
|
74
|
+
assert_equal "foo", @model.foo
|
75
|
+
end
|
76
|
+
|
77
|
+
should "not allow writing" do
|
78
|
+
assert_raises(NoMethodError) { @model.foo = "YO" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "default values" do
|
83
|
+
class DefaultModel < EmptyAttributeModel
|
84
|
+
attribute :foo, :default => "FOO!"
|
85
|
+
attribute :bar
|
86
|
+
end
|
87
|
+
|
88
|
+
setup do
|
89
|
+
@model = DefaultModel.new
|
90
|
+
end
|
91
|
+
|
92
|
+
should "read default values" do
|
93
|
+
assert_equal "FOO!", @model.foo
|
94
|
+
assert_nil @model.bar
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "populating attributes" do
|
100
|
+
setup do
|
101
|
+
@model = AttributeModel.new
|
102
|
+
end
|
103
|
+
|
104
|
+
should "write all valid attributes" do
|
105
|
+
new_attributes = {
|
106
|
+
:foo => "zxcv",
|
107
|
+
:bar => "qwerty"
|
108
|
+
}
|
109
|
+
|
110
|
+
@model.populate_attributes(new_attributes)
|
111
|
+
new_attributes.each do |k,v|
|
112
|
+
assert_equal v, @model.send(k)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "reading and writing attributes" do
|
118
|
+
setup do
|
119
|
+
@model = AttributeModel.new
|
120
|
+
@checkstring = "HEY"
|
121
|
+
end
|
122
|
+
|
123
|
+
should "be able to read an entire hash of attributes" do
|
124
|
+
atts = @model.attributes
|
125
|
+
assert atts.is_a?(Hash)
|
126
|
+
assert atts.has_key?(:foo)
|
127
|
+
assert atts.has_key?(:bar)
|
128
|
+
end
|
129
|
+
|
130
|
+
should "be able to write defined attributes" do
|
131
|
+
assert_nothing_raised {
|
132
|
+
@model.foo = @check_string
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
should "be able to read defined attributes" do
|
137
|
+
assert_nothing_raised {
|
138
|
+
assert_equal "foo", @model.foo
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
should "raise an error if attempting to write an undefined attribute" do
|
143
|
+
assert_raises(NoMethodError) { @model.baz = @check_string }
|
144
|
+
end
|
145
|
+
|
146
|
+
should "raise an error if attempting to read an undefined attribute" do
|
147
|
+
assert_raises(NoMethodError) { @model.baz }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|