rvc 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/rvc +53 -9
- data/lib/rvc/completion.rb +57 -19
- data/lib/rvc/extensions/ComputeResource.rb +2 -2
- data/lib/rvc/extensions/DVPortSetting.rb +108 -0
- data/lib/rvc/extensions/Datacenter.rb +19 -4
- data/lib/rvc/extensions/Datastore.rb +6 -1
- data/lib/rvc/extensions/DistributedVirtualPort.rb +146 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +274 -10
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +124 -3
- data/lib/rvc/extensions/Folder.rb +9 -2
- data/lib/rvc/extensions/HostSystem.rb +60 -0
- data/lib/rvc/extensions/ManagedEntity.rb +19 -0
- data/lib/rvc/extensions/ParaVirtualSCSIController.rb +25 -0
- data/lib/rvc/extensions/PerfCounterInfo.rb +26 -0
- data/lib/rvc/extensions/PerformanceManager.rb +83 -0
- data/lib/rvc/extensions/ResourcePool.rb +21 -0
- data/lib/rvc/extensions/VirtualDevice.rb +59 -0
- data/lib/rvc/extensions/VirtualDisk.rb +25 -0
- data/lib/rvc/extensions/VirtualEthernetCard.rb +32 -0
- data/lib/rvc/extensions/VirtualMachine.rb +112 -1
- data/lib/rvc/field.rb +122 -0
- data/lib/rvc/filesystem_session.rb +20 -0
- data/lib/rvc/inventory.rb +35 -12
- data/lib/rvc/known_hosts.rb +20 -0
- data/lib/rvc/memory_session.rb +20 -0
- data/lib/rvc/modules.rb +67 -7
- data/lib/rvc/modules/alarm.rb +37 -0
- data/lib/rvc/modules/basic.rb +172 -41
- data/lib/rvc/modules/cluster.rb +18 -2
- data/lib/rvc/modules/core.rb +63 -0
- data/lib/rvc/modules/datastore.rb +158 -0
- data/lib/rvc/modules/device.rb +275 -0
- data/lib/rvc/modules/esxcli.rb +193 -0
- data/lib/rvc/modules/find.rb +125 -0
- data/lib/rvc/modules/issue.rb +33 -0
- data/lib/rvc/modules/perf.rb +284 -0
- data/lib/rvc/modules/permissions.rb +20 -0
- data/lib/rvc/modules/resource_pool.rb +69 -0
- data/lib/rvc/modules/role.rb +23 -3
- data/lib/rvc/modules/snapshot.rb +20 -0
- data/lib/rvc/modules/vds.rb +605 -0
- data/lib/rvc/modules/vim.rb +103 -26
- data/lib/rvc/modules/vm.rb +93 -220
- data/lib/rvc/modules/vnc.rb +50 -13
- data/lib/rvc/option_parser.rb +50 -2
- data/lib/rvc/readline-ffi.rb +2 -1
- data/lib/rvc/shell.rb +34 -33
- data/lib/rvc/util.rb +120 -2
- data/test/test_fs.rb +9 -5
- data/test/test_metric.rb +79 -0
- metadata +33 -3
data/lib/rvc/modules/vnc.rb
CHANGED
@@ -18,7 +18,7 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
VNC = ENV['VNC'] || search_path('
|
21
|
+
VNC = ENV['VNC'] || search_path('tightvnc') || search_path('vncviewer') || search_path('vinagre')
|
22
22
|
|
23
23
|
opts :view do
|
24
24
|
summary "Spawn a VNC client"
|
@@ -29,8 +29,8 @@ rvc_alias :view, :vnc
|
|
29
29
|
rvc_alias :view, :V
|
30
30
|
|
31
31
|
def view vm
|
32
|
-
ip = reachable_ip vm.runtime.host
|
33
|
-
extraConfig = vm.config.extraConfig
|
32
|
+
ip = reachable_ip vm.collect('runtime.host')[0]
|
33
|
+
extraConfig, = vm.collect('config.extraConfig')
|
34
34
|
already_enabled = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' }
|
35
35
|
if already_enabled
|
36
36
|
puts "VNC already enabled"
|
@@ -68,7 +68,7 @@ end
|
|
68
68
|
|
69
69
|
|
70
70
|
def reachable_ip host
|
71
|
-
ips = host.config.network.vnic.map { |x| x.spec.ip.ipAddress }
|
71
|
+
ips = host.collect('config.network.vnic')[0].map { |x| x.spec.ip.ipAddress }
|
72
72
|
ips.find do |x|
|
73
73
|
begin
|
74
74
|
Timeout.timeout(1) { TCPSocket.new(x, 443).close; true }
|
@@ -95,17 +95,54 @@ def vnc_password
|
|
95
95
|
end
|
96
96
|
|
97
97
|
# Override this to spawn a VNC client differently
|
98
|
+
#
|
99
|
+
# We can save the vnc pasword out to a file, then call vncviewer with it
|
100
|
+
# directly so we don't need to "password" auth.
|
98
101
|
def vnc_client ip, port, password
|
99
|
-
|
100
|
-
fork do
|
101
|
-
$stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vmrc.log", "w")
|
102
|
-
Process.setpgrp
|
103
|
-
exec VNC, "#{ip}:#{port}"
|
104
|
-
end
|
105
|
-
puts "spawning #{VNC}"
|
106
|
-
puts "#{ip}:#{port} password: #{password}"
|
107
|
-
else
|
102
|
+
unless VNC
|
108
103
|
puts "no VNC client configured"
|
109
104
|
puts "#{ip}:#{port} password: #{password}"
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
|
108
|
+
if File.basename(VNC) == 'vncviewer' # or other vnc clients that support the same -passwd
|
109
|
+
tightvnc = %x(#{VNC} --version 2>&1).lines.first['TightVNC'] != nil
|
110
|
+
file = Tempfile.new('rvcvncpass')
|
111
|
+
filename = file.path
|
112
|
+
begin
|
113
|
+
if tightvnc
|
114
|
+
IO.popen("vncpasswd -f > #{filename}", 'w+') do |vncpass|
|
115
|
+
vncpass.puts password
|
116
|
+
vncpass.puts password
|
117
|
+
end
|
118
|
+
else
|
119
|
+
IO.popen("vncpasswd #{filename}", 'w+') do |vncpass|
|
120
|
+
vncpass.puts password
|
121
|
+
vncpass.puts password
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
vnc_client_connect ip, port, password, "-passwd #{filename}"
|
126
|
+
ensure
|
127
|
+
sleep 3 # we have to do this, as the vncviewer forks, and we've no simple way of working out if that thread has read the file yet.
|
128
|
+
file.close
|
129
|
+
file.unlink
|
130
|
+
end
|
131
|
+
else
|
132
|
+
vnc_client_connect ip, port, password
|
110
133
|
end
|
111
134
|
end
|
135
|
+
|
136
|
+
def vnc_client_connect ip, port, password, vnc_opts=nil
|
137
|
+
fork do
|
138
|
+
$stdout.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.log", "w")
|
139
|
+
$stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vnc.err", "w")
|
140
|
+
Process.setpgrp
|
141
|
+
exec [ VNC, vnc_opts, "#{ip}:#{port}" ].join ' '
|
142
|
+
end
|
143
|
+
puts "spawning #{VNC}"
|
144
|
+
print "#{ip}:#{port} password: #{password}"
|
145
|
+
print " options: #{vnc_opts}" unless vnc_opts.nil?
|
146
|
+
puts
|
147
|
+
end
|
148
|
+
|
data/lib/rvc/option_parser.rb
CHANGED
@@ -20,6 +20,13 @@
|
|
20
20
|
|
21
21
|
require 'trollop'
|
22
22
|
|
23
|
+
begin
|
24
|
+
require 'chronic'
|
25
|
+
RVC::HAVE_CHRONIC = true
|
26
|
+
rescue LoadError
|
27
|
+
RVC::HAVE_CHRONIC = false
|
28
|
+
end
|
29
|
+
|
23
30
|
module RVC
|
24
31
|
|
25
32
|
class OptionParser < Trollop::Parser
|
@@ -33,7 +40,10 @@ class OptionParser < Trollop::Parser
|
|
33
40
|
@seen_not_required = false
|
34
41
|
@seen_multi = false
|
35
42
|
@applicable = Set.new
|
36
|
-
super
|
43
|
+
super() do
|
44
|
+
opt :help, "Show this message", :short => 'h'
|
45
|
+
instance_eval &b
|
46
|
+
end
|
37
47
|
end
|
38
48
|
|
39
49
|
def summary str
|
@@ -47,7 +57,9 @@ class OptionParser < Trollop::Parser
|
|
47
57
|
|
48
58
|
def opt name, *a
|
49
59
|
super
|
50
|
-
|
60
|
+
spec = @specs[name]
|
61
|
+
@applicable << spec[:lookup] if spec[:lookup]
|
62
|
+
spec[:type] = :string if spec[:lookup] || spec[:lookup_parent]
|
51
63
|
@has_options = true unless name == :help
|
52
64
|
end
|
53
65
|
|
@@ -112,6 +124,16 @@ class OptionParser < Trollop::Parser
|
|
112
124
|
return args, opts
|
113
125
|
end
|
114
126
|
|
127
|
+
def parse_date_parameter param, arg
|
128
|
+
if RVC::HAVE_CHRONIC
|
129
|
+
Chronic.parse(param)
|
130
|
+
else
|
131
|
+
Time.parse param
|
132
|
+
end
|
133
|
+
rescue
|
134
|
+
raise ::Trollop::CommandlineError, "option '#{arg}' needs a time"
|
135
|
+
end
|
136
|
+
|
115
137
|
def postprocess_arg x, spec
|
116
138
|
if spec[:lookup]
|
117
139
|
RVC::Util.lookup!(x, spec[:lookup]).
|
@@ -138,4 +160,30 @@ class OptionParser < Trollop::Parser
|
|
138
160
|
end
|
139
161
|
end
|
140
162
|
|
163
|
+
class RawOptionParser
|
164
|
+
attr_reader :applicable
|
165
|
+
|
166
|
+
def initialize cmd, summary
|
167
|
+
@cmd = cmd
|
168
|
+
@summary = summary
|
169
|
+
@applicable = []
|
170
|
+
end
|
171
|
+
|
172
|
+
def summary?
|
173
|
+
@summary
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse args
|
177
|
+
[args, {}]
|
178
|
+
end
|
179
|
+
|
180
|
+
def has_options?
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
def educate
|
185
|
+
# XXX
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
141
189
|
end
|
data/lib/rvc/readline-ffi.rb
CHANGED
@@ -22,7 +22,8 @@ require 'ffi'
|
|
22
22
|
|
23
23
|
module RVC::ReadlineFFI
|
24
24
|
extend FFI::Library
|
25
|
-
|
25
|
+
libreadline = ENV['RVC_READLINE'] == nil ? 'readline.so' : ENV['RVC_READLINE']
|
26
|
+
ffi_lib libreadline
|
26
27
|
callback :rl_linebuf_func_t, [ :string, :int ], :bool
|
27
28
|
attach_variable :rl_char_is_quoted_p, :rl_char_is_quoted_p, :rl_linebuf_func_t
|
28
29
|
attach_variable :rl_line_buffer, :rl_line_buffer, :string
|
data/lib/rvc/shell.rb
CHANGED
@@ -58,7 +58,7 @@ class Shell
|
|
58
58
|
end
|
59
59
|
rescue SystemExit, IOError
|
60
60
|
raise
|
61
|
-
rescue RVC::Util::UserError, RuntimeError, RbVmomi::Fault
|
61
|
+
rescue RVC::Util::UserError, RuntimeError, RbVmomi::Fault, Trollop::CommandlineError
|
62
62
|
if ruby or debug
|
63
63
|
puts "#{$!.class}: #{$!.message}"
|
64
64
|
puts $!.backtrace * "\n"
|
@@ -73,51 +73,52 @@ class Shell
|
|
73
73
|
rescue Exception
|
74
74
|
puts "#{$!.class}: #{$!.message}"
|
75
75
|
puts $!.backtrace * "\n"
|
76
|
+
ensure
|
77
|
+
$stdout.flush
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
79
|
-
def
|
81
|
+
def self.parse_input input
|
80
82
|
cmd, *args = Shellwords.shellwords(input)
|
81
|
-
return unless cmd
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
elsif ALIASES.member? cmd
|
90
|
-
module_name, cmd, = ALIASES[cmd].split '.'
|
91
|
-
else
|
92
|
-
RVC::Util.err "unknown alias #{cmd}"
|
93
|
-
end
|
94
|
-
|
95
|
-
m = MODULES[module_name] or RVC::Util.err("unknown module #{module_name}")
|
83
|
+
return nil unless cmd
|
84
|
+
if cmd.include? '.'
|
85
|
+
module_name, cmd, = cmd.split '.'
|
86
|
+
elsif ALIASES.member? cmd
|
87
|
+
module_name, cmd, = ALIASES[cmd].split '.'
|
88
|
+
end
|
89
|
+
[MODULES[module_name], cmd.to_sym, args]
|
90
|
+
end
|
96
91
|
|
97
|
-
|
98
|
-
|
92
|
+
def eval_command input
|
93
|
+
m, cmd, args = Shell.parse_input input
|
94
|
+
RVC::Util.err "invalid command" unless m != nil and
|
95
|
+
cmd.is_a? Symbol and
|
96
|
+
m.has_command? cmd
|
97
|
+
parser = m.opts_for(cmd)
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
begin
|
100
|
+
args, opts = parser.parse args
|
101
|
+
rescue Trollop::HelpNeeded
|
102
|
+
parser.educate
|
103
|
+
return
|
104
|
+
end
|
106
105
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
106
|
+
if parser.has_options?
|
107
|
+
m.send cmd.to_sym, *(args + [opts])
|
108
|
+
else
|
109
|
+
m.send cmd.to_sym, *args
|
112
110
|
end
|
113
|
-
nil
|
114
111
|
end
|
115
112
|
|
116
113
|
def eval_ruby input, file="<input>"
|
117
114
|
result = @ruby_evaluator.do_eval input, file
|
118
115
|
if $interactive
|
119
116
|
if input =~ /\#$/
|
120
|
-
|
117
|
+
if result.is_a? Class
|
118
|
+
introspect_class result
|
119
|
+
else
|
120
|
+
introspect_object result
|
121
|
+
end
|
121
122
|
else
|
122
123
|
pp result
|
123
124
|
end
|
@@ -205,7 +206,7 @@ class RubyEvaluator
|
|
205
206
|
rescue Exception => e
|
206
207
|
bt = e.backtrace
|
207
208
|
bt = bt.reverse.drop_while { |x| !(x =~ /toplevel/) }.reverse
|
208
|
-
bt[-1].gsub! ':in `toplevel\'', ''
|
209
|
+
bt[-1].gsub! ':in `toplevel\'', '' if bt[-1]
|
209
210
|
e.set_backtrace bt
|
210
211
|
raise
|
211
212
|
end
|
data/lib/rvc/util.rb
CHANGED
@@ -18,6 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'delegate'
|
22
|
+
|
21
23
|
module RVC
|
22
24
|
module Util
|
23
25
|
extend self
|
@@ -104,6 +106,7 @@ module Util
|
|
104
106
|
end
|
105
107
|
|
106
108
|
def progress tasks
|
109
|
+
results = {}
|
107
110
|
interested = %w(info.progress info.state info.entityName info.error info.name)
|
108
111
|
connection = single_connection tasks
|
109
112
|
connection.serviceInstance.wait_for_multiple_tasks interested, tasks do |h|
|
@@ -120,8 +123,10 @@ module Util
|
|
120
123
|
$stdout.write "\e[K#{text}#{progress_bar}\n"
|
121
124
|
elsif state == 'error'
|
122
125
|
error = props['info.error']
|
126
|
+
results[task] = error
|
123
127
|
$stdout.write "\e[K#{name} #{entityName}: #{error.fault.class.wsdl_name}: #{error.localizedMessage}\n"
|
124
128
|
else
|
129
|
+
results[task] = task.info.result if state == 'success'
|
125
130
|
$stdout.write "\e[K#{name} #{entityName}: #{state}\n"
|
126
131
|
end
|
127
132
|
end
|
@@ -130,7 +135,13 @@ module Util
|
|
130
135
|
end
|
131
136
|
end
|
132
137
|
$stdout.write "\e[#{tasks.size}B" if interactive?
|
133
|
-
|
138
|
+
results
|
139
|
+
end
|
140
|
+
|
141
|
+
def one_progress task
|
142
|
+
progress([task])[task].tap do |r|
|
143
|
+
raise r if r.is_a? VIM::LocalizedMethodFault
|
144
|
+
end
|
134
145
|
end
|
135
146
|
|
136
147
|
def terminal_columns
|
@@ -160,7 +171,7 @@ module Util
|
|
160
171
|
tcsetpgrp
|
161
172
|
exec cmd
|
162
173
|
end
|
163
|
-
Process.waitpid2 pid
|
174
|
+
Process.waitpid2 pid rescue nil
|
164
175
|
tcsetpgrp
|
165
176
|
nil
|
166
177
|
end
|
@@ -194,5 +205,112 @@ module Util
|
|
194
205
|
|
195
206
|
Hash[results.map { |r| [r['name'], r.obj] }]
|
196
207
|
end
|
208
|
+
|
209
|
+
def status_color str, status
|
210
|
+
$terminal.color(str, *VIM::ManagedEntity::STATUS_COLORS[status])
|
211
|
+
end
|
212
|
+
|
213
|
+
def metric num
|
214
|
+
MetricNumber.new(num.to_f, '', false).to_s
|
215
|
+
end
|
216
|
+
|
217
|
+
def retrieve_fields objs, fields
|
218
|
+
Hash[objs.map do |o|
|
219
|
+
begin
|
220
|
+
[o, Hash[fields.map { |f| [f, o.field(f)] }]]
|
221
|
+
rescue VIM::ManagedObjectNotFound
|
222
|
+
next
|
223
|
+
end
|
224
|
+
end]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class Numeric
|
230
|
+
def metric
|
231
|
+
RVC::Util.metric self
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class TimeDiff < SimpleDelegator
|
236
|
+
def to_s
|
237
|
+
i = self.to_i
|
238
|
+
seconds = i % 60
|
239
|
+
i /= 60
|
240
|
+
minutes = i % 60
|
241
|
+
i /= 60
|
242
|
+
hours = i
|
243
|
+
[hours, minutes, seconds].join ':'
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.parse str
|
247
|
+
a = str.split(':', 3).reverse
|
248
|
+
seconds = a[0].to_i rescue 0
|
249
|
+
minutes = a[1].to_i rescue 0
|
250
|
+
hours = a[2].to_i rescue 0
|
251
|
+
TimeDiff.new(hours * 3600 + minutes * 60 + seconds)
|
252
|
+
end
|
197
253
|
end
|
254
|
+
|
255
|
+
class MetricNumber < SimpleDelegator
|
256
|
+
attr_reader :unit, :binary
|
257
|
+
|
258
|
+
def initialize val, unit, binary=false
|
259
|
+
@unit = unit
|
260
|
+
@binary = binary
|
261
|
+
super val.to_f
|
262
|
+
end
|
263
|
+
|
264
|
+
def to_s
|
265
|
+
limit = @binary ? 1024 : 1000
|
266
|
+
if self < limit
|
267
|
+
prefix = ''
|
268
|
+
multiple = 1
|
269
|
+
else
|
270
|
+
prefixes = @binary ? BINARY_PREFIXES : DECIMAL_PREFIXES
|
271
|
+
prefixes = prefixes.sort_by { |k,v| v }
|
272
|
+
prefix, multiple = prefixes.find { |k,v| self/v < limit }
|
273
|
+
prefix, multiple = prefixes.last unless prefix
|
274
|
+
end
|
275
|
+
("%0.2f %s%s" % [self/multiple, prefix, @unit]).strip
|
276
|
+
end
|
277
|
+
|
278
|
+
# http://physics.nist.gov/cuu/Units/prefixes.html
|
279
|
+
DECIMAL_PREFIXES = {
|
280
|
+
'k' => 10 ** 3,
|
281
|
+
'M' => 10 ** 6,
|
282
|
+
'G' => 10 ** 9,
|
283
|
+
'T' => 10 ** 12,
|
284
|
+
'P' => 10 ** 15,
|
285
|
+
}
|
286
|
+
|
287
|
+
# http://physics.nist.gov/cuu/Units/binary.html
|
288
|
+
BINARY_PREFIXES = {
|
289
|
+
'Ki' => 2 ** 10,
|
290
|
+
'Mi' => 2 ** 20,
|
291
|
+
'Gi' => 2 ** 30,
|
292
|
+
'Ti' => 2 ** 40,
|
293
|
+
'Pi' => 2 ** 50,
|
294
|
+
}
|
295
|
+
|
296
|
+
CANONICAL_PREFIXES = Hash[(DECIMAL_PREFIXES.keys + BINARY_PREFIXES.keys).map { |x| [x.downcase, x] }]
|
297
|
+
|
298
|
+
def self.parse str
|
299
|
+
if str =~ /^([0-9,.]+)\s*([kmgtp]i?)?/i
|
300
|
+
x = $1.delete(',').to_f
|
301
|
+
binary = false
|
302
|
+
if $2
|
303
|
+
prefix = $2.downcase
|
304
|
+
binary = prefix[1..1] == 'i'
|
305
|
+
prefixes = binary ? BINARY_PREFIXES : DECIMAL_PREFIXES
|
306
|
+
multiple = prefixes[CANONICAL_PREFIXES[prefix]]
|
307
|
+
else
|
308
|
+
multiple = 1
|
309
|
+
end
|
310
|
+
units = $'
|
311
|
+
new x*multiple, units, binary
|
312
|
+
else
|
313
|
+
raise "Problem parsing SI number #{str.inspect}"
|
314
|
+
end
|
315
|
+
end
|
198
316
|
end
|
data/test/test_fs.rb
CHANGED
@@ -18,16 +18,20 @@ class FSTest < Test::Unit::TestCase
|
|
18
18
|
|
19
19
|
def setup
|
20
20
|
@context = RVC::FS.new Root
|
21
|
+
session = RVC::MemorySession.new
|
22
|
+
$shell = RVC::Shell.new session
|
23
|
+
$shell.instance_variable_set :@fs, @context
|
21
24
|
end
|
22
25
|
|
23
26
|
def teardown
|
24
27
|
@context = nil
|
28
|
+
$shell = nil
|
25
29
|
end
|
26
30
|
|
27
31
|
def test_new
|
28
32
|
assert_equal Root, @context.cur
|
29
33
|
assert_equal "", @context.display_path
|
30
|
-
assert_equal 0,
|
34
|
+
assert_equal 0, $shell.session.marks.size
|
31
35
|
assert_equal [['', Root]], @context.cur.rvc_path
|
32
36
|
end
|
33
37
|
|
@@ -93,20 +97,20 @@ class FSTest < Test::Unit::TestCase
|
|
93
97
|
assert_equal nil, obj
|
94
98
|
|
95
99
|
['foo', '~', '7', ''].each do |mark|
|
96
|
-
|
100
|
+
$shell.session.set_mark mark, [b_obj]
|
97
101
|
obj = @context.lookup("~#{mark}")[0]
|
98
102
|
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
|
99
103
|
|
100
|
-
|
104
|
+
$shell.session.set_mark mark, []
|
101
105
|
obj = @context.lookup("~#{mark}")[0]
|
102
106
|
assert_equal nil, obj
|
103
107
|
end
|
104
108
|
|
105
|
-
|
109
|
+
$shell.session.set_mark '7', [b_obj]
|
106
110
|
obj = @context.lookup("7")[0]
|
107
111
|
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
|
108
112
|
|
109
|
-
|
113
|
+
$shell.session.set_mark '7', []
|
110
114
|
obj = @context.lookup("7")[0]
|
111
115
|
assert_equal nil, obj
|
112
116
|
end
|