net-snmp 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -25,11 +25,12 @@ You can use the library in two styles: synchronously or asynchronously. If you
25
25
  calls will block until a result is returned or the request times out. If you use the asynchronous style, you
26
26
  must provide a block to the client methods, which will be called when the data is ready.
27
27
 
28
- In order for your callbacks to fire, you must call Net::SNMP.dispatcher. You may pass a timeout argument to
28
+ In order for your callbacks to fire, you must call Net::SNMP::Dispatcher.poll You may pass a timeout argument to
29
29
  the dispatcher. If you pass false, the call will block forever until data is ready. If you pass an integer, it will block for
30
30
  <timeout> seconds. If you pass nil (the default), it will do a poll and return immediately. In that case, you
31
31
  will have to arrange for the dispatcher to run periodically. This can be done in your main event loop or in a
32
- seperate thread.
32
+ seperate thread. There are convenience class methods in Net::SNMP::Dispatcher called run_loop, fiber_loop, em_loop, thread_loop
33
+ and so on for setting up various polling loops. Or, you can roll your own loop.
33
34
 
34
35
  If you like eventmachine, it's very easy to set up the dispatcher to run periodically in the adaptor. If you're
35
36
  using eventmachine with ruby 1.9, the library uses fibers behind the scenes to turn your synchronous calls into asynchronous
@@ -57,36 +58,16 @@ A simple synchronous SNMP-GET
57
58
  result.free # you have to manually free the memory in sync calls for now.
58
59
 
59
60
  An asynchronous SNMP-GET
60
-
61
61
  Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |session|
62
62
  session.get(["sysDescr.0", "sysContact.0"]) do |result|
63
63
  puts result.varbinds[0].value
64
64
  end
65
65
  end
66
- Net::SNMP.dispatcher(false) #Setting timeout to false causes dispatcher to block until data is ready
67
-
68
-
69
- Running the dispatcher in a seperate thread
70
-
71
- dispatcher_thread = Thread.new do
72
- while Net::SNMP.dispatcher == 0
73
- sleep 1
74
- end
75
- end
76
-
77
- Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
78
- s.get(["sysDescr.0", "sysContact.0"]) do |result|
79
- puts result.varbinds[0].value
80
- end
81
- end
82
- dispatcher_thread.join
66
+ Net::SNMP::Dispatcher.poll(false) #Setting timeout to false causes dispatcher to block until data is ready
83
67
 
84
68
  Running the dispatcher from eventmachine
85
69
  EM.run do
86
- tickloop = EM.tick_loop do
87
- Net::SNMP.dispatcher
88
- end
89
-
70
+ Net::SNMP::Dispatcher.em_loop
90
71
  Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |session|
91
72
  session.get("sysDescr.0") do |result|
92
73
  puts result.varbinds[0].value
@@ -96,16 +77,13 @@ Running the dispatcher from eventmachine
96
77
 
97
78
  Using synchronous style with eventmachine in ruby 1.9
98
79
  EM.run do
80
+ Net::SNMP::Dispatcher.fiber_loop
99
81
  Fiber.new {
100
- EM.tick_loop do
101
- Net::SNMP.dispatcher
102
- end
103
82
  session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic')
104
83
  result = session.get("sysDescr.0")
105
84
  puts result.varbinds[0].value
106
-
85
+ EM.stop
107
86
  }.resume
108
- EM.stop
109
87
  end
110
88
 
111
89
 
@@ -128,12 +106,19 @@ Having the appropriate MIBs loaded will allow you to pass names instead of numer
128
106
  you can call Net::SNMP.get_oid("sysDescr.0"). For more complex MIB parsing needs, see smi-ffi[http://github.com/mixtli/smi-ffi]
129
107
 
130
108
 
109
+ == BUGS
110
+ Right now, snmpv3 is only working using synchronous API. Asynchronous calls seem to miss packets.
111
+ I have no idea why it would be different for v3 vs v1/v2. If anyone knows, tell me ;)
112
+
113
+
131
114
  == CAVEATS/DISCLAIMER
132
115
 
133
116
  THIS GEM COULD CRASH YOUR SYSTEM AND EAT YOUR CHILDREN!
134
117
 
135
118
  You have been warned.
136
119
 
120
+ The API is subject to change.
121
+
137
122
  This is very much alpha software. For more information on usage, take a look at the specs. Currently it's
138
123
  only tested against net-snmp 5.5. The net-snmp api seems pretty stable, but if anything changes, it could
139
124
  break this gem. Please let me know if you find bugs or missing features. Or better yet, send a pull request.
@@ -0,0 +1,103 @@
1
+ class Net::SNMP::Dispatcher
2
+ class << self
3
+
4
+ # timeout = nil no block(poll), timeout = false block forever, timeout = int, block int seconds
5
+ def poll(timeout = nil)
6
+ fdset = Net::SNMP::Wrapper.get_fd_set
7
+ num_fds = FFI::MemoryPointer.new(:int)
8
+ tv_sec = timeout ? timeout.round : 0
9
+ tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
10
+ tval = Net::SNMP::Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
11
+ block = FFI::MemoryPointer.new(:int)
12
+
13
+ if timeout.nil?
14
+ block.write_int(0)
15
+ else
16
+ block.write_int(1)
17
+ end
18
+ #puts "calling snmp_select_info"
19
+ Net::SNMP::Wrapper.snmp_select_info(num_fds, fdset, tval.pointer, block )
20
+ #puts "done snmp_select_info."
21
+ num_ready = 0
22
+ #puts "block = #{block.read_int}"
23
+
24
+ #puts "numready = #{num_fds.read_int}"
25
+ #puts "tv = #{tval[:tv_sec]} #{tval[:tv_usec]}"
26
+ #puts "timeout = #{timeout}"
27
+ tv = (timeout == false ? nil : tval)
28
+ #puts "calling select"
29
+ #puts "tv = #{tv.inspect}"
30
+ #puts "calling select with #{num_fds.read_int}"
31
+ num_ready = Net::SNMP::Wrapper.select(num_fds.read_int, fdset, nil, nil, tv)
32
+ #puts "done select. num_ready = #{num_ready}"
33
+ if num_ready > 0
34
+ Net::SNMP::Wrapper.snmp_read(fdset)
35
+ elsif num_ready == 0
36
+ # timeout. do something here? or just return 0?
37
+ elsif num_ready == -1
38
+ # error. check snmp_error?
39
+ else
40
+ # uhhh
41
+ end
42
+ #puts "done snmp_read"
43
+ num_ready
44
+ end
45
+
46
+
47
+ def run_loop(options = {})
48
+ if defined?(EM) && EM.reactor_running?
49
+ fiber_loop(options)
50
+ else
51
+ thread_loop(options)
52
+ end
53
+ end
54
+
55
+ # You should not pass nil to this as it will peg your cpu
56
+ def thread_loop(options = {})
57
+
58
+ timeout = options[:timeout] || false
59
+ sleep_time = options[:sleep]
60
+ Thread.new do
61
+ loop do
62
+ poll(timeout)
63
+ sleep(sleep_time) if sleep_time
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ def em_loop(options = {})
70
+ timeout = options[:timeout]
71
+ sleep_time = options[:sleep_time] || 0.1
72
+ myproc = Proc.new do
73
+ EM.next_tick do
74
+ while(true) do
75
+ num_ready = poll(timeout)
76
+ break if num_ready == 0
77
+ end
78
+ EM.add_timer(sleep_time) do
79
+ myproc.call
80
+ end
81
+ end
82
+ end
83
+ myproc.call
84
+ end
85
+
86
+ def fiber_loop(options = {})
87
+ timeout = options[:timeout]
88
+ sleep_time = options[:sleep] || 0.01
89
+ Fiber.new {
90
+ loop do
91
+ num_handled = poll(timeout)
92
+ if num_handled == 0
93
+ f = Fiber.current
94
+ EM.add_timer(sleep_time) do
95
+ f.resume
96
+ end
97
+ Fiber.yield
98
+ end
99
+ end
100
+ }.resume(nil)
101
+ end
102
+ end
103
+ end
data/lib/net/snmp/oid.rb CHANGED
@@ -21,6 +21,9 @@ module Net
21
21
 
22
22
  end
23
23
 
24
+ def size
25
+ @length_pointer.read_int
26
+ end
24
27
  def oid
25
28
  @oid
26
29
  end
data/lib/net/snmp/pdu.rb CHANGED
@@ -3,7 +3,7 @@ module Net
3
3
  class PDU
4
4
  extend Forwardable
5
5
  attr_accessor :struct, :varbinds, :callback
6
- def_delegators :struct, :reqid, :pointer, :errstat, :errindex, :non_repeaters=
6
+ def_delegators :struct, :pointer
7
7
 
8
8
  def initialize(arg)
9
9
  @varbinds = []
@@ -38,30 +38,63 @@ module Net
38
38
  def max_repetitions
39
39
  @struct.errindex
40
40
  end
41
+ def enterprise=(e_oid)
42
+ @enterprise = e_iod
43
+ @struct.enterprise = e_oid.pointer
44
+ @struct.enterprise_length = e_oid.size
45
+ end
46
+ def enterprise
47
+ @enterprise
48
+ end
49
+
50
+ def agent_addr=(addr)
51
+ @struct.agent_addr = addr.split('.').pack("CCCC")
52
+ @agent_addr = addr
53
+ end
41
54
 
55
+ def agent_addr
56
+ @agent_addr
57
+ end
42
58
 
43
- def add_varbind(options)
44
59
 
45
- options[:type] ||= case options[:value]
46
- when String
47
- Constants::ASN_OCTET_STR
48
- when nil
49
- Constants::ASN_NULL
60
+ def method_missing(m, *args)
61
+ if @struct.respond_to?(m)
62
+ @struct.send(m, *args)
50
63
  else
51
- raise "Unknown value type"
64
+ super
52
65
  end
66
+ end
53
67
 
68
+ def add_varbind(options)
69
+ options[:type] ||= case options[:value]
70
+ when String
71
+ Constants::ASN_OCTET_STR
72
+ when Fixnum
73
+ Constants::ASN_INTEGER
74
+ when nil
75
+ Constants::ASN_NULL
76
+ else
77
+ raise "Unknown value type"
78
+ end
79
+
80
+ value = options[:value]
54
81
  value_len = case options[:value]
55
- when String
56
- options[:value].length
57
- else
58
- 0
82
+ when String
83
+ options[:value].length
84
+ when nil
85
+ 0
86
+ else
87
+ options[:value].size
59
88
  end
60
89
 
61
90
 
91
+ if value.respond_to?(:pointer)
92
+ value = value.pointer
93
+ end
94
+
62
95
  oid = Net::SNMP::OID.new(options[:oid])
63
96
 
64
- var_ptr = Wrapper.snmp_pdu_add_variable(@struct.pointer, oid.pointer, oid.length_pointer.read_int, options[:type], options[:value], value_len)
97
+ var_ptr = Wrapper.snmp_pdu_add_variable(@struct.pointer, oid.pointer, oid.length_pointer.read_int, options[:type], value, value_len)
65
98
  varbind = Varbind.new(var_ptr)
66
99
  #Wrapper.print_varbind(varbind.struct)
67
100
  varbinds << varbind
@@ -5,6 +5,7 @@ module Net
5
5
  class Session
6
6
  extend Forwardable
7
7
  attr_accessor :struct, :callback
8
+ attr_reader :version
8
9
  def_delegator :@struct, :pointer
9
10
  #@sessions = []
10
11
  @requests = {}
@@ -25,6 +26,7 @@ module Net
25
26
  options[:community_len] = options[:community].length
26
27
  options[:version] ||= Constants::SNMP_VERSION_1
27
28
  @callback = options[:callback]
29
+ @version = options[:version] || 1
28
30
  #self.class.sessions << self
29
31
  @sess = Wrapper::SnmpSession.new(nil)
30
32
  Wrapper.snmp_sess_init(@sess.pointer)
@@ -43,7 +45,6 @@ module Net
43
45
  Constants::SNMP_VERSION_1
44
46
  end
45
47
 
46
-
47
48
  if options[:timeout]
48
49
  @sess.timeout = options[:timeout] * 1000000
49
50
  end
@@ -51,7 +52,6 @@ module Net
51
52
  @sess.retries = options[:retries]
52
53
  end
53
54
 
54
-
55
55
  if @sess.version == Constants::SNMP_VERSION_3
56
56
  @sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
57
57
 
@@ -70,7 +70,6 @@ module Net
70
70
  if options[:context]
71
71
  @sess.contextName = FFI::MemoryPointer.from_string(options[:context])
72
72
  @sess.contextNameLen = options[:context].length
73
-
74
73
  end
75
74
 
76
75
  if options[:username]
@@ -84,13 +83,19 @@ module Net
84
83
  unless key_result == Constants::SNMPERR_SUCCESS
85
84
  Wrapper.snmp_perror("netsnmp")
86
85
  end
87
-
88
86
  end
89
87
 
90
88
  # General callback just takes the pdu, calls the session callback if any, then the request specific callback.
91
89
  @sess.callback = lambda do |operation, session, reqid, pdu_ptr, magic|
92
- pdu = Net::SNMP::PDU.new(pdu_ptr)
93
- run_callbacks(operation, reqid, pdu, magic)
90
+ callback.call(operation, reqid, pdu, magic) if callback
91
+
92
+ puts "in main callback"
93
+ if self.class.requests[reqid]
94
+ puts "got request"
95
+ pdu = Net::SNMP::PDU.new(pdu_ptr)
96
+ self.class.requests[reqid].call(pdu)
97
+ self.class.requests.delete(reqid)
98
+ end
94
99
  0
95
100
  end
96
101
 
@@ -99,15 +104,6 @@ module Net
99
104
  #@struct = Wrapper.snmp_sess_session(@handle)
100
105
  end
101
106
 
102
-
103
-
104
- def run_callbacks(operation, reqid, pdu, magic)
105
- callback.call(operation, reqid, pdu, magic) if callback
106
- if self.class.requests[reqid]
107
- self.class.requests[reqid].call(pdu)
108
- self.class.requests.delete(reqid)
109
- end
110
- end
111
107
 
112
108
 
113
109
  def get(oidlist, options = {}, &block)
@@ -132,6 +128,7 @@ module Net
132
128
  pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GETBULK)
133
129
  oidlist = [oidlist] unless oidlist.kind_of?(Array)
134
130
  oidlist.each do |oid|
131
+ puts "adding #{oid.inspect}"
135
132
  pdu.add_varbind(:oid => oid)
136
133
  end
137
134
  pdu.non_repeaters = options[:non_repeaters] || 0
@@ -193,6 +190,27 @@ module Net
193
190
  results
194
191
  end
195
192
 
193
+
194
+ def default_max_repeaters
195
+ # We could do something based on transport here. 25 seems safe
196
+ 25
197
+ end
198
+
199
+ def get_columns(columns, options = {})
200
+ column_oids = columns.map {|c| Net::SNMP::OID.new(c)}
201
+ options[:max_repetitions] ||= default_max_repeaters / columns.size
202
+ results = {}
203
+ if version == 1
204
+ else
205
+ while(result = get_bulk(columns, options))
206
+ result.varbinds.each do |vb|
207
+ match = column_oids.select {|c| vb.oid.oid =~ /^#{c.oid}/ }
208
+ end
209
+ end
210
+
211
+ end
212
+ end
213
+
196
214
  def error(msg)
197
215
  Wrapper.snmp_perror("snmp_error")
198
216
  Wrapper.snmp_sess_perror( "snmp_error", @sess.pointer)
@@ -200,24 +218,6 @@ module Net
200
218
  raise Net::SNMP::Error.new({:session => self}), msg
201
219
  end
202
220
 
203
- # def dispatcher
204
- # fdset = Net::SNMP::Wrapper.get_fd_set
205
- # num_fds = FFI::MemoryPointer.new(:int)
206
- # tval = Net::SNMP::Wrapper::TimeVal.new
207
- # block = FFI::MemoryPointer.new(:int)
208
- # block.write_int(0)
209
- #
210
- # # Note.. for some reason, snmp_sess_select_info changes block to be 1.
211
- # Net::SNMP::Wrapper.snmp_sess_select_info(@handle, num_fds, fdset, tval.pointer, block )
212
- # if num_fds.read_int > 0
213
- # zero = Wrapper::TimeVal.new(:tv_sec => 0, :tv_usec => 0)
214
- # #Wrapper.print_timeval(zero)
215
- #
216
- # num_ready = Net::SNMP::Wrapper.select(num_fds.read_int, fdset, nil, nil, zero.pointer)
217
- # Net::SNMP::Wrapper.snmp_sess_read(@handle, fdset)
218
- # end
219
- # end
220
-
221
221
 
222
222
  private
223
223
  def send_pdu(pdu, &block)
@@ -252,8 +252,67 @@ module Net
252
252
  end
253
253
  end
254
254
  end
255
+ end
256
+
257
+ # def get_entries_cb(pdu, columns, options)
258
+ # cache = {}
259
+ # row_index = nil
260
+ # varbinds = pdu.varbinds.dup
261
+ # while(varbinds.size > 0)
262
+ # row = {}
263
+ # columns.each do |column|
264
+ # vb = varbinds.shift
265
+ # if vb.oid.to_s =~ /#{column.to_s}\.(\d+(:?\.\d+)*)/
266
+ # index = $1
267
+ # else
268
+ # last_entry = true
269
+ # next
270
+ # end
271
+ # row_index = index unless row_index
272
+ # index_cmp = Net::SNMP.oid_lex_cmp(index, row_index)
273
+ # if(index_cmp == 0)
274
+ # end
275
+ # end
276
+ # end
277
+ # end
278
+
279
+
280
+
281
+
282
+ def trap(options = {})
283
+ pdu = PDU.new(Constants::SNMP_MSG_TRAP)
284
+ options[:enterprise] ||= '1.3.6.1.4.1.3.1.1' # uh, just send netsnmp enterprise i guess
285
+ pdu.enterprise = OID.new(options[:enterprise])
286
+ pdu.trap_type = options[:trap_type] || 1 # need to check all these defaults
287
+ pdu.specific_type = options[:specific_type] || 0
288
+ pdu.time = 1 # put what here?
289
+ send_pdu(pdu)
290
+ end
255
291
 
292
+
293
+ def trap_v2(options = {})
294
+ pdu = PDU.new(Constants::SNMP_MSG_TRAP2)
295
+ build_trap_pdu(options)
296
+ send_pdu(pdu)
297
+ end
298
+
299
+ def inform(options = {}, &block)
300
+ pdu = PDU.new(Constants::SNMP_MSG_INFORM)
301
+ build_trap_pdu(options)
302
+ send_pdu(pdu, &block)
303
+ end
304
+
305
+ def build_trap_pdu(pdu, options = {})
306
+ pdu.add_varbind(:oid => OID.new('sysUpTime'), :type => Constants::ASN_TIMETICKS, :value => 0)
307
+ pdu.add_varbind(:oid => OID.new('snmpTrapOID'), :type => Constants::ASN_OBJECT_ID, :value => options[:oid])
308
+ if options[:varbinds]
309
+ options[:varbinds].each do |vb|
310
+ pdu.add_varbind(vb)
311
+ end
312
+ end
256
313
  end
314
+
315
+
257
316
  end
258
317
  end
259
318
  end
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  module SNMP
3
- VERSION = "0.1.4"
3
+ VERSION = "0.1.5"
4
4
  end
5
5
  end
@@ -251,6 +251,7 @@ module Wrapper
251
251
  # attach_function :netsnmp_oid_tree_equals, [ :pointer, :uint, :pointer, :uint ], :int
252
252
  attach_function :netsnmp_oid_is_subtree, [ :pointer, :uint, :pointer, :uint ], :int
253
253
  attach_function :netsnmp_oid_find_prefix, [ :pointer, :uint, :pointer, :uint ], :int
254
+ attach_function :netsnmp_transport_open_client, [:string, :pointer], :pointer
254
255
  attach_function :init_snmp, [ :string ], :void
255
256
  attach_function :snmp_pdu_build, [ :pointer, :pointer, :pointer ], :pointer
256
257
  attach_function :snmpv3_parse, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :int
data/lib/net/snmp.rb CHANGED
@@ -8,39 +8,11 @@ module Net
8
8
  Wrapper.init_snmp(tag)
9
9
  end
10
10
 
11
- # timeout = nil no block(poll), timeout = false block forever, timeout = int, block int seconds
12
- def self.dispatcher(timeout = nil)
13
- fdset = Net::SNMP::Wrapper.get_fd_set
14
- num_fds = FFI::MemoryPointer.new(:int)
15
- tv_sec = timeout || 0
16
- tval = Net::SNMP::Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => 0)
17
- block = FFI::MemoryPointer.new(:int)
18
-
19
- if timeout.nil?
20
- block.write_int(0)
21
- else
22
- block.write_int(1)
23
- end
24
- #puts "calling snmp_select_info"
25
- Net::SNMP::Wrapper.snmp_select_info(num_fds, fdset, tval.pointer, block )
26
- #puts "done snmp_select_info."
27
- num_ready = 0
28
- #puts "block = #{block.read_int}"
29
-
30
- #puts "numready = #{num_fds.read_int}"
31
- #puts "tv = #{tval[:tv_sec]} #{tval[:tv_usec]}"
32
- if num_fds.read_int > 0
33
- tv = timeout == false ? nil : tval
34
- #puts "calling select"
35
- num_ready = Net::SNMP::Wrapper.select(num_fds.read_int, fdset, nil, nil, tv)
36
- #puts "done select. num_ready = #{num_ready}"
37
- Net::SNMP::Wrapper.snmp_read(fdset)
38
- else
39
- end
40
- num_ready
41
- end
42
11
 
43
12
 
13
+
14
+ # Some utility methods
15
+
44
16
  def self._get_oid(name)
45
17
  oid_ptr = FFI::MemoryPointer.new(:ulong, Constants::MAX_OID_LEN)
46
18
  oid_len_ptr = FFI::MemoryPointer.new(:size_t)
@@ -57,6 +29,15 @@ module Net
57
29
  oid_ptr.read_array_of_long(oid_len_ptr.read_int).join(".")
58
30
  end
59
31
 
32
+ def self.oid_lex_cmp(a,b)
33
+ [a,b].each do |i|
34
+ i.sub!(/^\./,'')
35
+ i.gsub!(/ /, '.0')
36
+ i.replace(i.split('.').map(&:to_i).pack('N*'))
37
+ end
38
+ a <=> b
39
+ end
40
+
60
41
 
61
42
  end
62
43
  end
data/lib/net-snmp.rb CHANGED
@@ -2,7 +2,7 @@ require 'forwardable'
2
2
  require 'nice-ffi'
3
3
  require 'fiber'
4
4
 
5
- %w(snmp snmp/version snmp/constants snmp/oid snmp/error snmp/pdu snmp/wrapper snmp/session snmp/varbind snmp/mib snmp/mib/node).each do |f|
5
+ %w(snmp snmp/version snmp/constants snmp/oid snmp/error snmp/pdu snmp/wrapper snmp/session snmp/varbind snmp/mib snmp/mib/node snmp/dispatcher).each do |f|
6
6
  require "#{File.dirname(__FILE__)}/net/#{f}"
7
7
  end
8
8
  Net::SNMP::MIB.init
data/net-snmp.gemspec CHANGED
@@ -5,7 +5,7 @@ $:.unshift lib
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "net-snmp"
8
- s.version = '0.1.4'
8
+ s.version = '0.1.5'
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.authors = ["Ron McClain"]
11
11
  s.email = ["mixtli@github.com"]
@@ -0,0 +1,106 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "async" do
4
+ context "version 1" do
5
+ it "get should work" do
6
+ did_callback = false
7
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
8
+ s.get(["sysDescr.0", "sysContact.0"]) do |result|
9
+ did_callback = true
10
+ result.varbinds[0].value.should eql("test.net-snmp.org")
11
+ result.varbinds[1].value.should match(/Coders/)
12
+ end
13
+ end
14
+ Net::SNMP::Dispatcher.poll(false)
15
+ did_callback.should be(true)
16
+ end
17
+
18
+ it "getnext should work" do
19
+ did_callback = false
20
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
21
+ s.get_next(["sysDescr", "sysContact"]) do |result|
22
+ did_callback = true
23
+ result.varbinds[0].value.should eql("test.net-snmp.org")
24
+ result.varbinds[1].value.should match(/Coders/)
25
+ end
26
+ end
27
+ Net::SNMP::Dispatcher.poll(false)
28
+ did_callback.should be(true)
29
+ end
30
+
31
+ end
32
+
33
+ context "version 2" do
34
+ it "get should work" do
35
+ did_callback = false
36
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic', :version => '2c') do |s|
37
+ s.get(["sysDescr.0", "sysContact.0"]) do |result|
38
+ did_callback = true
39
+ result.varbinds[0].value.should eql("test.net-snmp.org")
40
+ result.varbinds[1].value.should match(/Coders/)
41
+ end
42
+ end
43
+ Net::SNMP::Dispatcher.poll(false)
44
+ did_callback.should be(true)
45
+ end
46
+
47
+ it "getnext should work" do
48
+ did_callback = false
49
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic', :version => '2c') do |s|
50
+ s.get_next(["sysDescr", "sysContact"]) do |result|
51
+ did_callback = true
52
+ result.varbinds[0].value.should eql("test.net-snmp.org")
53
+ result.varbinds[1].value.should match(/Coders/)
54
+ end
55
+ end
56
+ Net::SNMP::Dispatcher.poll(false)
57
+ did_callback.should be(true)
58
+ end
59
+ end
60
+
61
+ context "version 3" do
62
+ #failing intermittently
63
+ it "should get async using snmpv3" do
64
+ did_callback = false
65
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User',:security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
66
+ sess.get(["sysDescr.0"]) do |result|
67
+ did_callback = true
68
+ result.varbinds[0].value.should eql('test.net-snmp.org')
69
+ end
70
+ sleep(0.5)
71
+ Net::SNMP::Dispatcher.poll(false)
72
+ #Net::SNMP::Dispatcher.poll(false)
73
+ puts "done poll"
74
+ did_callback.should be(true)
75
+ end
76
+ end
77
+
78
+
79
+ it "get should work" do
80
+ did_callback = false
81
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User',:security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
82
+ sess.get(["sysDescr.0", "sysContact.0"]) do |result|
83
+ did_callback = true
84
+ result.varbinds[0].value.should eql("test.net-snmp.org")
85
+ result.varbinds[1].value.should match(/Coders/)
86
+ end
87
+ end
88
+ Net::SNMP::Dispatcher.poll(false)
89
+ did_callback.should be(true)
90
+ end
91
+
92
+ it "getnext should work" do
93
+ did_callback = false
94
+
95
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User',:security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
96
+ sess.get_next(["sysDescr", "sysContact"]) do |result|
97
+ did_callback = true
98
+ result.varbinds[0].value.should eql("test.net-snmp.org")
99
+ result.varbinds[1].value.should match(/Coders/)
100
+ end
101
+ end
102
+ Net::SNMP::Dispatcher.poll(false)
103
+ did_callback.should be(true)
104
+ end
105
+ end
106
+ end
data/spec/em_spec.rb ADDED
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "em" do
4
+
5
+ it "should work in event_machine" do
6
+ require 'eventmachine'
7
+ did_callback = false
8
+ EM.run do
9
+ Net::SNMP::Dispatcher.em_loop
10
+
11
+ session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
12
+ s.get("sysDescr.0") do |result|
13
+ did_callback = true
14
+ result.varbinds[0].value.should eql("test.net-snmp.org")
15
+ end
16
+ end
17
+
18
+ EM.add_timer(3) do
19
+ did_callback.should be_true
20
+ EM.stop
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'eventmachine'
3
+ require 'fiber'
4
+
5
+ describe "in fiber" do
6
+
7
+ def wrap_fiber
8
+ EM.run do
9
+ Net::SNMP::Dispatcher.fiber_loop
10
+ Fiber.new { yield; EM.stop }.resume(nil)
11
+ end
12
+ end
13
+
14
+
15
+ it "get should work in a fiber with synchronous calling style" do
16
+ wrap_fiber do
17
+ session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic')
18
+ result = session.get("sysDescr.0")
19
+ puts "got result = #{result}"
20
+ result.varbinds[0].value.should eql("test.net-snmp.org")
21
+ end
22
+ end
23
+
24
+ it "getnext" do
25
+ wrap_fiber do
26
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
27
+ result = sess.get_next(["sysUpTimeInstance.0"])
28
+ result.varbinds.first.oid.oid.should eql("1.3.6.1.2.1.1.4.0")
29
+ result.varbinds.first.value.should match(/Net-SNMP Coders/)
30
+ end
31
+ end
32
+ end
33
+
34
+ it "should get using snmpv3" do
35
+ wrap_fiber do
36
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User', :security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
37
+ result = sess.get(["sysDescr.0"])
38
+ result.varbinds.first.value.should eql('test.net-snmp.org')
39
+ end
40
+ end
41
+ end
42
+
43
+ end
@@ -1,35 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "NetSnmp" do
4
- it "should get single oid" do
5
- Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
6
- result = sess.get("sysDescr.0")
7
- result.varbinds.first.value.should eql("test.net-snmp.org")
8
- end
9
- end
10
-
11
- it "should get multiple oids" do
12
- Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => 'demopublic' ) do |sess|
13
- result = sess.get(["sysDescr.0", "sysName.0"])
14
- result.varbinds[0].value.should eql("test.net-snmp.org")
15
- result.varbinds[1].value.should eql("test.net-snmp.org")
16
- end
17
- end
18
-
19
- it "should get multiple requests asynchronously" do
20
- did_callback = false
21
- session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
22
- s.get(["sysDescr.0", "sysContact.0"]) do |result|
23
- did_callback = true
24
- result.varbinds[0].value.should eql("test.net-snmp.org")
25
- result.varbinds[1].value.should match(/Coders/)
26
- end
27
- end
28
- Net::SNMP.dispatcher(false)
29
- did_callback.should be(true)
30
- end
31
-
32
-
33
4
  it "should get an oid asynchronously in a thread" do
34
5
  did_callback = false
35
6
  session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
@@ -40,7 +11,7 @@ describe "NetSnmp" do
40
11
  end
41
12
  end
42
13
  Thread.new do
43
- while Net::SNMP.dispatcher == 0
14
+ while Net::SNMP::Dispatcher.poll == 0
44
15
  sleep 1
45
16
  end
46
17
  end.join
@@ -48,101 +19,24 @@ describe "NetSnmp" do
48
19
  end
49
20
 
50
21
 
51
- it "invalid oid should return error" do
52
- Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
53
- result = sess.get(["XXXsysDescr.0"]) #misspelled
54
- result.should be_error
55
- end
56
- end
57
-
58
- it "should get using snmpv3" do
59
- Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User', :security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
60
- result = sess.get("sysDescr.0")
61
- result.varbinds.first.value.should eql('test.net-snmp.org')
62
- end
63
- end
64
-
65
- it "should get async using snmpv3" do
66
- did_callback = false
67
- Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User',:security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
68
- sess.get(["sysDescr.0", "sysName.0"]) do |result|
69
- did_callback = true
70
- result.varbinds[0].value.should eql('test.net-snmp.org')
71
- result.varbinds[1].value.should eql('test.net-snmp.org')
72
- 0
73
- end
74
- sleep(1)
75
- Net::SNMP.dispatcher()
76
- did_callback.should be(true)
77
- end
78
- end
79
-
80
-
81
- # To test sets, you have to have a local snmpd running with write permissions
82
- it "should set a value" do
83
- Net::SNMP::Session.open(:peername => '127.0.0.1', :version => 1) do |sess|
84
- result = sess.set([['sysContact.0', Net::SNMP::Constants::ASN_OCTET_STR, 'yomama']])
85
- result.varbinds.first.value.should match(/yomama/)
86
- result.should_not be_error
87
- end
88
- end
89
-
90
- #it "should set using snmpv3" do
91
- # pending
92
- # Net::SNMP::Session.open(:peername => '127.0.0.1', :version => 3, :username => 'myuser', :authprotocol => :sha1, :authkey => '0x1234', :privprotocol => :des, :privkey => '0x25252') do |sess|
93
- # result = sess.set([["sysDescr.0", Net::SNMP::Constants::ASN_OCTET_STR, 'yomama']])
94
- # result.varbinds.first.value.should match(/Darwin/)
95
- # end
96
- #end
97
-
98
- it "should get next an array of oids" do
99
- Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
100
- result = sess.get_next(["sysUpTimeInstance.0"])
101
- result.varbinds.first.oid.oid.should eql("1.3.6.1.2.1.1.4.0")
102
- result.varbinds.first.value.should match(/Net-SNMP Coders/)
103
- end
104
- end
105
-
22
+ # it "should get columns" do
23
+ # Net::SNMP::Session.open(:peername => 'localhost', :version => '2c', :community => 'public') do |sess|
24
+ # result = sess.get_columns(['ifIndex', 'ifInOctets', 'ifOutOctets'])
25
+ # result.size.should eql(7)
26
+ # result['1']['ifIndex'].should eql('1')
27
+ # result['2']['ifInOctets'].should be > 10
28
+ # end
29
+ # end
106
30
 
107
- it "should get_bulk_request" do
108
- Net::SNMP::Session.open(:peername => "test.net-snmp.org" , :version => '2c', :community => 'demopublic') do |sess|
109
- result = sess.get_bulk(["sysContact.0"], :max_repetitions => 10)
110
- result.varbinds.first.oid.name.should eql("1.3.6.1.2.1.1.5.0")
111
- result.varbinds.first.value.should eql("test.net-snmp.org")
112
- end
113
- end
114
-
115
- it "should get a table of values with explicit columns" do
116
- session = Net::SNMP::Session.open(:peername => "localhost", :version => '2c')
117
- table = session.get_table("ifTable", :columns => ["ifIndex", "ifDescr", "ifName"])
118
- table[0]['ifName'].should eql("lo0")
119
- table[1]['ifName'].should eql("gif0")
120
- end
121
31
 
122
- it "should get a table of values" do
123
- session = Net::SNMP::Session.open(:peername => "localhost", :version => '2c')
124
- table = session.get_table("ifEntry")
125
- table[0]['ifIndex'].should eql(1)
126
- table[1]['ifIndex'].should eql(2)
32
+ it "should compare oids" do
33
+ Net::SNMP.oid_lex_cmp('1.3.5', '1.3.7').should eql(-1)
34
+ Net::SNMP.oid_lex_cmp('1.3.7', '1.3.5').should eql(1)
35
+ Net::SNMP.oid_lex_cmp('1.3.7', '1.3.7.1').should eql(-1)
36
+ Net::SNMP.oid_lex_cmp('1.3.5', '1.3.5').should eql(0)
127
37
  end
128
38
 
129
- it "should get a table of values ipAddrEntry" do
130
- puts Net::SNMP::Constants::ASN_IPADDRESS
131
- session = Net::SNMP::Session.open(:peername => "localhost", :version => '2c')
132
- table = session.get_table("ipAddrEntry")
133
- puts table.inspect
134
- table[0]['ipAdEntAddr'].should eql('127.0.0.1')
135
- table[1]['ipAdEntNetMask'].should eql('255.255.255.0')
136
- end
137
39
 
138
- it "should get a table of values ifXEntry" do
139
- puts Net::SNMP::Constants::ASN_IPADDRESS
140
- session = Net::SNMP::Session.open(:peername => "localhost", :version => '2c')
141
- table = session.get_table("ifXEntry")
142
- puts table.inspect
143
- table[0]['ifName'].should eql('lo0')
144
- table[3]['ifHighSpeed'].should eql(1000)
145
- end
146
40
 
147
41
  it "should translate an oid" do
148
42
 
@@ -154,74 +48,9 @@ describe "NetSnmp" do
154
48
 
155
49
 
156
50
 
157
- it "should work in event_machine" do
158
- require 'eventmachine'
159
- did_callback = false
160
- EM.run do
161
- tickloop = EM.tick_loop do
162
- Net::SNMP.dispatcher
163
- end
164
51
 
165
- session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
166
- s.get("sysDescr.0") do |result|
167
- did_callback = true
168
- result.varbinds[0].value.should eql("test.net-snmp.org")
169
- end
170
-
171
- end
172
52
 
173
- EM.add_timer(3) do
174
- did_callback.should be_true
175
- EM.stop
176
- end
177
- end
178
- end
179
-
180
- it "should work in a fiber" do
181
- require 'eventmachine'
182
- did_callback = false
183
- EM.run do
184
- tickloop = EM.tick_loop do
185
- Net::SNMP.dispatcher
186
- end
187
53
 
188
- Fiber.new {
189
- session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic') do |s|
190
- s.get("sysDescr.0") do |result|
191
- did_callback = true
192
- result.varbinds[0].value.should eql("test.net-snmp.org")
193
- end
194
-
195
- end
196
-
197
- EM.add_timer(3) do
198
- did_callback.should eql(true)
199
- EM.stop
200
- end
201
- }.resume
202
- end
203
- end
204
-
205
-
206
-
207
- it "get should work in a fiber with synchronous calling style" do
208
- require 'eventmachine'
209
- did_callback = false
210
- EM.run do
211
- Fiber.new {
212
-
213
- EM.tick_loop do
214
-
215
- Net::SNMP.dispatcher
216
- end
217
- sleep 1
218
- session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'demopublic')
219
- result = session.get("sysDescr.0")
220
- result.varbinds[0].value.should eql("test.net-snmp.org")
221
- }.resume
222
- EM.stop
223
- end
224
- end
225
54
 
226
55
 
227
56
  end
data/spec/sync_spec.rb ADDED
@@ -0,0 +1,95 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "synchronous calls" do
4
+ # To test sets, you have to have a local snmpd running with write permissions
5
+ context "version 1" do
6
+ it "get should succeed" do
7
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
8
+ result = sess.get("sysDescr.0")
9
+ result.varbinds.first.value.should eql("test.net-snmp.org")
10
+ end
11
+ end
12
+
13
+ it "get should succeed with multiple oids" do
14
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => 'demopublic' ) do |sess|
15
+ result = sess.get(["sysDescr.0", "sysName.0"])
16
+ result.varbinds[0].value.should eql("test.net-snmp.org")
17
+ result.varbinds[1].value.should eql("test.net-snmp.org")
18
+ end
19
+ end
20
+
21
+ it "set should succeed" do
22
+ Net::SNMP::Session.open(:peername => '127.0.0.1', :version => 1) do |sess|
23
+ result = sess.set([['sysContact.0', Net::SNMP::Constants::ASN_OCTET_STR, 'yomama']])
24
+ result.varbinds.first.value.should match(/yomama/)
25
+ result.should_not be_error
26
+ end
27
+ end
28
+
29
+ it "getnext should succeed" do
30
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
31
+ result = sess.get_next(["sysUpTimeInstance.0"])
32
+ result.varbinds.first.oid.oid.should eql("1.3.6.1.2.1.1.4.0")
33
+ result.varbinds.first.value.should match(/Net-SNMP Coders/)
34
+ end
35
+ end
36
+
37
+
38
+ it "getbulk should succeed" do
39
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org" , :version => '2c', :community => 'demopublic') do |sess|
40
+ result = sess.get_bulk(["sysContact.0"], :max_repetitions => 10)
41
+ result.varbinds.first.oid.name.should eql("1.3.6.1.2.1.1.5.0")
42
+ result.varbinds.first.value.should eql("test.net-snmp.org")
43
+ end
44
+ end
45
+
46
+ it "getbulk should succeed with multiple oids" do
47
+ Net::SNMP::Session.open(:peername => "localhost" , :version => '2c', :community => 'public') do |sess|
48
+ result = sess.get_bulk(["ifInOctets", 'ifOutOctets', 'ifInErrors', 'ifOutErrors'], :max_repeaters =>10)
49
+ result.varbinds.size.should eql(40)
50
+ end
51
+ end
52
+
53
+ it "get should return error with invalid oid" do
54
+ Net::SNMP::Session.open(:peername => "test.net-snmp.org", :community => "demopublic" ) do |sess|
55
+ result = sess.get(["XXXsysDescr.0"]) #misspelled
56
+ result.should be_error
57
+ end
58
+ end
59
+
60
+ it "get_table should work with multiple columns" do
61
+ session = Net::SNMP::Session.open(:peername => "localhost", :version => '1')
62
+ table = session.get_table("ifTable", :columns => ["ifIndex", "ifDescr", "ifName"])
63
+ table[0]['ifName'].should eql("lo0")
64
+ table[1]['ifName'].should eql("gif0")
65
+ end
66
+
67
+ it "get_table should work" do
68
+ session = Net::SNMP::Session.open(:peername => "localhost", :version => '1')
69
+ table = session.get_table("ifEntry")
70
+ table[0]['ifIndex'].should eql(1)
71
+ table[1]['ifIndex'].should eql(2)
72
+ end
73
+
74
+ end
75
+
76
+ context "version 2" do
77
+
78
+ end
79
+
80
+ context "version 3" do
81
+ it "should get using snmpv3" do
82
+ Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 3, :username => 'MD5User', :security_level => Net::SNMP::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV, :auth_protocol => :md5, :password => 'The Net-SNMP Demo Password') do |sess|
83
+ result = sess.get("sysDescr.0")
84
+ result.varbinds.first.value.should eql('test.net-snmp.org')
85
+ end
86
+ end
87
+ #it "should set using snmpv3" do
88
+ # pending
89
+ # Net::SNMP::Session.open(:peername => '127.0.0.1', :version => 3, :username => 'myuser', :authprotocol => :sha1, :authkey => '0x1234', :privprotocol => :des, :privkey => '0x25252') do |sess|
90
+ # result = sess.set([["sysDescr.0", Net::SNMP::Constants::ASN_OCTET_STR, 'yomama']])
91
+ # result.varbinds.first.value.should match(/Darwin/)
92
+ # end
93
+ #end
94
+ end
95
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 4
9
- version: 0.1.4
8
+ - 5
9
+ version: 0.1.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ron McClain
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-04-21 00:00:00 -05:00
17
+ date: 2011-04-24 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -69,6 +69,7 @@ files:
69
69
  - lib/net-snmp.rb
70
70
  - lib/net/snmp.rb
71
71
  - lib/net/snmp/constants.rb
72
+ - lib/net/snmp/dispatcher.rb
72
73
  - lib/net/snmp/error.rb
73
74
  - lib/net/snmp/inline.rb
74
75
  - lib/net/snmp/mib.rb
@@ -80,9 +81,13 @@ files:
80
81
  - lib/net/snmp/version.rb
81
82
  - lib/net/snmp/wrapper.rb
82
83
  - net-snmp.gemspec
84
+ - spec/async_spec.rb
85
+ - spec/em_spec.rb
86
+ - spec/fiber_spec.rb
83
87
  - spec/mib_spec.rb
84
88
  - spec/net-snmp_spec.rb
85
89
  - spec/spec_helper.rb
90
+ - spec/sync_spec.rb
86
91
  - spec/wrapper_spec.rb
87
92
  has_rdoc: true
88
93
  homepage: ""
@@ -117,7 +122,11 @@ signing_key:
117
122
  specification_version: 3
118
123
  summary: Object oriented wrapper around C net-snmp libraries
119
124
  test_files:
125
+ - spec/async_spec.rb
126
+ - spec/em_spec.rb
127
+ - spec/fiber_spec.rb
120
128
  - spec/mib_spec.rb
121
129
  - spec/net-snmp_spec.rb
122
130
  - spec/spec_helper.rb
131
+ - spec/sync_spec.rb
123
132
  - spec/wrapper_spec.rb