1# Test::SNMP::Info
2#
3# Copyright (c) 2018 Eric Miller
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9#     * Redistributions of source code must retain the above copyright notice,
10#       this list of conditions and the following disclaimer.
11#     * Redistributions in binary form must reproduce the above copyright
12#       notice, this list of conditions and the following disclaimer in the
13#       documentation and/or other materials provided with the distribution.
14#     * Neither the name of the University of California, Santa Cruz nor the
15#       names of its contributors may be used to endorse or promote products
16#       derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22# LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29
30package Test::SNMP::Info;
31
32use strict;
33use warnings;
34use Test::Class::Most parent => 'My::Test::Class';
35
36use SNMP::Info;
37
38sub constructor : Tests(+3) {
39  my $test = shift;
40  $test->SUPER::constructor;
41
42  is($test->{info}{snmp_comm}, 'public',  'SNMP comm arg saved');
43  is($test->{info}{snmp_ver},  2,         'SNMP version arg saved');
44  is($test->{info}{snmp_user}, 'initial', 'SNMP user arg saved');
45}
46
47sub update : Tests(9) {
48  my $test = shift;
49
50  can_ok($test->{info}, 'update');
51
52  # Starting community
53  is($test->{info}{sess}{Community}, 'public', q(Original community 'public'));
54
55  # Change community
56  my %update_args = ('Community' => 'new_community',);
57  delete $test->{info}{args}{Session};
58  ok($test->{info}->update(%update_args), 'Update community');
59  is($test->{info}->error(),         undef,           '... and no error');
60  is($test->{info}{sess}{Community}, 'new_community', 'Community changed');
61
62TODO: {
63    # The update() method creates a new SNMP::Session, v1/2 do not actually
64    # need to contact the DestHost for session creation while v3 does.
65    # It appears that Net-SNMP 5.8 changes the behavior of v3 session creation
66    # so that it doesn't require contact with the DestHost to pass these tests
67    # We also could connect to http://snmplabs.com v3 simulator but would
68    # prefer to keep those tests isolated to 10_remote_snmplabs.t - we could
69    # also move the update() tests to that file.
70
71    my $version = $SNMP::VERSION;
72    my ( $major, $minor, $rev ) = split( '\.', $version );
73
74    todo_skip "Skip v3 Context update() tests when using Net-SNMP < 5.8", 4
75      if ($major < 5 or $minor < 8);
76
77    # Starting context
78    ok(!defined $test->{info}{sess}{Context}, q(Context doesn't exist));
79
80    # Change context
81    # Since update() is actually creating new SNMP::Session we can put
82    # whatever session arguments needed in %update_args
83    %update_args = ('Context' => 'vlan-100', 'Version' => 3,);
84    ok($test->{info}->update(%update_args), 'Update Context');
85    is($test->{info}->error(),         undef,      '... and no error');
86    is($test->{info}->{sess}{Context}, 'vlan-100', 'Context changed');
87
88  }
89}
90
91sub cache_and_clear_cache : Tests(9) {
92  my $test = shift;
93
94  # Isolate tests to cache method. Populated structure of global 'name' and
95  # func 'i_description'
96  my $cache_data = {
97    '_name'          => 'Test-Name',
98    '_i_description' => 1,
99    'store'          => {
100      'i_description' =>
101        {10 => 'Test-Description-10', 20 => 'Test-Description-20'}
102    }
103  };
104
105  # The empty store hash exists upon initialization and remains when the cache
106  # is cleared.
107  my $empty_cache = {'store' => {}};
108
109  can_ok($test->{info}, 'cache');
110  cmp_deeply($empty_cache, $test->{info}->cache(), 'Cache starts empty');
111  ok($test->{info}->cache($cache_data), 'Insert test data into cache');
112  cmp_deeply(
113    $cache_data,
114    $test->{info}->cache(),
115    'Cache method returns test data'
116  );
117  is($test->{info}->name(),
118    'Test-Name', 'Global method call returned cached data');
119  cmp_deeply(
120    $test->{info}->i_description(),
121    $cache_data->{store}{i_description},
122    'Funcs method call returned cached data'
123  );
124  can_ok($test->{info}, 'clear_cache');
125  ok($test->{info}->clear_cache(), 'cache cleared');
126  cmp_deeply(
127    $empty_cache,
128    $test->{info}->cache(),
129    'No cached data returned after clear_cache method call'
130  );
131}
132
133sub debug : Tests(4) {
134  my $test = shift;
135
136  can_ok($test->{info}, 'debug');
137
138  ok(
139    defined $test->{info}{debug}
140      && $test->{info}{debug} == 0
141      && $test->{info}->debug() == 0,
142    'Debug initialized off'
143  );
144  $test->{info}->debug(1);
145  ok($test->{info}{debug} && $test->{info}->debug(), 'Debug on');
146  $test->{info}->debug(0);
147  ok($test->{info}{debug} == 0 && $test->{info}->debug() == 0, 'Debug off');
148}
149
150sub offline : Tests(4) {
151  my $test = shift;
152
153  can_ok($test->{info}, 'offline');
154
155  ok(!defined $test->{info}{Offline}, 'Offline not initialized');
156  $test->{info}->offline(1);
157  ok($test->{info}{Offline} && $test->{info}->offline(), 'Offline mode on');
158  $test->{info}->offline(0);
159  ok($test->{info}{Offline} == 0 && $test->{info}->offline() == 0,
160    'Offline off');
161}
162
163sub bulkwalk : Tests(4) {
164  my $test = shift;
165
166  can_ok $test->{info}, 'bulkwalk';
167
168  # Test harness initalizes BulkWalk off, if we didn't provide an arg
169  # it would not be defined.
170  ok(
171    !defined $test->{info}{BulkWalk}
172      || ($test->{info}{BulkWalk} == 0 && $test->{info}->bulkwalk() == 0),
173    'Bulkwalk initialized off'
174  );
175  $test->{info}->bulkwalk(1);
176  ok($test->{info}{BulkWalk} && $test->{info}->bulkwalk(), 'Bulkwalk on');
177  $test->{info}->bulkwalk(0);
178  ok($test->{info}{BulkWalk} == 0 && $test->{info}->bulkwalk() == 0,
179    'Bulkwalk off');
180}
181
182sub loopdetect : Tests(4) {
183  my $test = shift;
184
185  can_ok $test->{info}, 'loopdetect';
186
187  ok(!defined $test->{info}{LoopDetect}, 'Loopdetect not initialized');
188  $test->{info}->loopdetect(1);
189  ok($test->{info}{LoopDetect} && $test->{info}->loopdetect(), 'Loopdetect on');
190  $test->{info}->loopdetect(0);
191  ok($test->{info}{LoopDetect} == 0 && $test->{info}->loopdetect() == 0,
192    'Loopdetect off');
193}
194
195sub device_type : Tests(+6) {
196  my $test = shift;
197  $test->SUPER::device_type();
198
199  # No sysServices and unknown sysDescr results in SNMP::Info
200  my $cache_data
201    = {'_layers' => '00000000', '_description' => 'My-Test-sysDescr',};
202  $test->{info}->cache($cache_data);
203
204  $test->{info}->debug(1);
205  warnings_like { $test->{info}->device_type() }
206  [{carped => qr/Might give unexpected results/i}],
207    'No sysServices and unknown sysDescr with debug on gives warning';
208  $test->{info}->debug(0);
209  $test->{info}->clear_cache();
210
211  # Cache has been cleared, empty args and no SNMP data result in undef
212  is($test->{info}->device_type(),
213    undef, 'No sysServices, no sysDescr results in undef');
214
215  # Test one oid per layer hash just to verify oid mapping, no need to test
216  # every hash key - chose an id that is unique per layer
217
218  # Layer 3
219  $cache_data = {
220    '_layers'      => 4,
221    '_description' => 'My-Test-sysDescr',
222    '_id'          => '.1.3.6.1.4.1.18'
223  };
224  $test->{info}->cache($cache_data);
225  is($test->{info}->device_type,
226    'SNMP::Info::Layer3::BayRS', 'Layer 3 device type by sysObjectID');
227  $test->{info}->clear_cache();
228
229  # Layer 2
230  $cache_data = {
231    '_layers'      => 2,
232    '_description' => 'My-Test-sysDescr',
233    '_id'          => '.1.3.6.1.4.1.11898'
234  };
235  $test->{info}->cache($cache_data);
236  is($test->{info}->device_type,
237    'SNMP::Info::Layer2::Orinoco', 'Layer 2 device type by sysObjectID');
238  $test->{info}->clear_cache();
239
240  # Layer 1
241  $cache_data = {
242    '_layers'      => 1,
243    '_description' => 'My-Test-sysDescr',
244    '_id'          => '.1.3.6.1.4.1.2925'
245  };
246  $test->{info}->cache($cache_data);
247  is(
248    $test->{info}->device_type,
249    'SNMP::Info::Layer1::Cyclades',
250    'Layer 1 device type by sysObjectID'
251  );
252  $test->{info}->clear_cache();
253
254  # Layer 7
255  $cache_data = {
256    '_layers'      => 64,
257    '_description' => 'My-Test-sysDescr',
258    '_id'          => '.1.3.6.1.4.1.318'
259  };
260  $test->{info}->cache($cache_data);
261  is($test->{info}->device_type,
262    'SNMP::Info::Layer7::APC', 'Layer 7 device type by sysObjectID');
263  $test->{info}->clear_cache();
264
265  # We will test each specific subclass, so no need to check that logic here
266}
267
268sub error : Tests(7) {
269  my $test = shift;
270
271  can_ok($test->{info}, 'error');
272  ok(!exists $test->{info}{error}, 'Error not present');
273  $test->{info}{error} = 'Test Error';
274  is($test->{info}->error(), 'Test Error', 'Test Error present');
275  is($test->{info}->error(), undef,        'Test Error cleared upon read');
276  $test->{info}{error} = 'Test Error 2';
277  is($test->{info}->error(1),
278    'Test Error 2', 'Test Error 2 present and no clear flag set');
279  is($test->{info}->error(0),
280    'Test Error 2', 'Test Error 2 still present on next read');
281  is($test->{info}->error(),
282    undef, 'Test Error 2 cleared upon read with flag set to false');
283}
284
285sub has_layer : Tests(6) {
286  my $test = shift;
287
288  can_ok $test->{info}, 'has_layer';
289  $test->{info}->clear_cache();
290
291  # Populate cache, one key/value so don't bother going through the
292  # cache() method.
293  # Layers holds the unmunged value (decimal)
294  $test->{info}{'_layers'} = 1;
295  is($test->{info}->has_layer(1), 1, 'Has layer 1');
296
297  $test->{info}{'_layers'} = 2;
298  is($test->{info}->has_layer(2), 1, 'Has layer 2');
299
300  $test->{info}{'_layers'} = 4;
301  is($test->{info}->has_layer(3), 1, 'Has layer 3');
302
303  # We don't use layers 4-6 for classification, skip testing
304
305  $test->{info}{'_layers'} = 64;
306  is($test->{info}->has_layer(7), 1, 'Has layer 7');
307
308  # Check for undef layers
309  $test->{info}{'_layers'} = undef;
310  is($test->{info}->has_layer(7), undef, 'Undef layers returns undef');
311}
312
313sub snmp_comm : Tests(4) {
314  my $test = shift;
315
316  can_ok $test->{info}, 'snmp_comm';
317
318  # Define before test to be sure instead of relying on initalization
319  $test->{info}{snmp_comm} = 'publicv1';
320  $test->{info}{snmp_ver}  = 1;
321  is($test->{info}->snmp_comm(), 'publicv1',
322    'Version 1 returns SNMP community');
323
324  $test->{info}{snmp_comm} = 'publicv2';
325  $test->{info}{snmp_ver}  = 2;
326  is($test->{info}->snmp_comm(), 'publicv2',
327    'Version 2 returns SNMP community');
328
329  $test->{info}{snmp_user} = 'initialv3';
330  $test->{info}{snmp_ver}  = 3;
331  is($test->{info}->snmp_comm(), 'initialv3', 'Version 3 returns SNMP user');
332}
333
334sub snmp_ver : Tests(2) {
335  my $test = shift;
336
337  can_ok $test->{info}, 'snmp_ver';
338
339  # Define before test to be sure instead of relying on initalization
340  $test->{info}{snmp_ver} = 1;
341  is($test->{info}->snmp_ver(), 1, 'SNMP version returned');
342}
343
344sub specify : Tests(4) {
345  my $test = shift;
346
347  can_ok($test->{info}, 'specify');
348  $test->{info}->cache_clear();
349
350  # Specify uses device_type(), use same data as that test to setup
351  # test cases here since return values from device_type() with them
352  # have been tested
353
354  # device_type returns undef
355  $test->{info}->specify();
356  is(
357    $test->{info}->error(),
358    'SNMP::Info::specify() - Could not get info from device',
359    'Undef device type throws error'
360  );
361  $test->{info}->cache_clear();
362
363  # Populate cache for following tests
364  my $cache_data
365    = {'_layers' => '00000000', '_description' => 'My-Test-sysDescr',};
366  $test->{info}->cache($cache_data);
367
368  isa_ok($test->{info}->specify(),
369    'SNMP::Info', 'SNMP::Info device_type returns self');
370  $test->{info}->cache_clear();
371
372  # Layer 7 - SNMP::Info::Layer7::APC
373  $cache_data = {
374    '_layers'      => 64,
375    '_description' => 'My-Test-sysDescr',
376    '_id'          => '.1.3.6.1.4.1.318'
377  };
378  $test->{info}->cache($cache_data);
379  isa_ok($test->{info}->specify(),
380    'SNMP::Info::Layer7::APC',
381    'Layer 7 device type returns new object of same type');
382  $test->{info}->clear_cache();
383}
384
385sub cisco_comm_indexing : Tests(2) {
386  my $test = shift;
387
388  can_ok $test->{info}, 'cisco_comm_indexing';
389  is($test->{info}->cisco_comm_indexing(), 0, 'Cisco community indexing off');
390}
391
392sub if_ignore : Tests(2) {
393  my $test = shift;
394
395  can_ok $test->{info}, 'if_ignore';
396  cmp_deeply($test->{info}->if_ignore(),
397    {}, 'No ignored interfaces for this class');
398}
399
400sub bulkwalk_no : Tests(2) {
401  my $test = shift;
402
403  can_ok $test->{info}, 'bulkwalk_no';
404  is($test->{info}->bulkwalk_no(), 0, 'Bulkwalk not turned off in this class');
405}
406
407sub i_speed : Tests(2) {
408  my $test = shift;
409
410  can_ok $test->{info}, 'i_speed';
411
412  # Method uses partial fetches which ignores the cache and reloads data
413  # therefore we must use the mocked session. Populate the session data
414  # so that the mock_getnext() has data to fetch.
415  my $data = {
416
417    # Need to use OID for ifSpeed since it could resolve to a fully qualified
418    # name as either RFC1213-MIB::ifSpeed or IF-MIB::ifSpeed dependent upon
419    # which MIB got loaded last which is based upon random hash ordering. Using
420    # a fully qualified name with mock session we would need to know which MIB
421    # "owned" the OID since the MIB hash is indexed by OID. This is not an
422    # issue in live code since what is fed to getnext for a fully qualified
423    # name is what is returned.
424    '.1.3.6.1.2.1.2.2.1.5' => {38 => 0, 49 => 4294967295, 501 => 1000000000,},
425    'IF-MIB::ifHighSpeed'  => {38 => 0, 49 => 32000,      501 => 1000,},
426  };
427  my $expected = {38 => 0, 49 => '32 Gbps', 501 => '1.0 Gbps',};
428  $test->{info}{sess}{Data} = $data;
429  cmp_deeply($test->{info}->i_speed(),
430    $expected, 'High speed interface reported accurately');
431}
432
433sub i_speed_raw : Tests(3) {
434  my $test = shift;
435
436  can_ok $test->{info}, 'i_speed_raw';
437
438  # Method uses partial fetches which ignores the cache and reloads data
439  # therefore we must use the mocked session. Populate the session data
440  # so that the mock_getnext() has data to fetch.
441  my $data = {
442
443    # Need to use OID for ifSpeed since it could resolve to a fully qualified
444    # name as either RFC1213-MIB::ifSpeed or IF-MIB::ifSpeed dependent upon
445    # which MIB got loaded last which is based upon random hash ordering. Using
446    # a fully qualified name with mock session we would need to know which MIB
447    # "owned" the OID since the MIB hash is indexed by OID. This is not an
448    # issue in live code since what is fed to getnext for a fully qualified
449    # name is what is returned.
450    '.1.3.6.1.2.1.2.2.1.5' => {38 => 0, 49 => 4294967295, 501 => 1000000000,},
451    'IF-MIB::ifHighSpeed'  => {38 => 0, 49 => 32000,      501 => 1000,},
452  };
453  my $expected     = {38 => 0, 49 => '32 Gbps',   501 => '1.0 Gbps',};
454  my $expected_raw = {38 => 0, 49 => 32000000000, 501 => 1000000000,};
455  $test->{info}{sess}{Data} = $data;
456  cmp_deeply($test->{info}->i_speed_raw(),
457    $expected_raw, 'Raw high speed interface reported accurately');
458
459  # Note the cache is populated unmunged data now - not sure if that is
460  # expected behavior. Clear cache to get data to test that munges are restored.
461  $test->{info}->clear_cache();
462  cmp_deeply($test->{info}->i_speed(),
463    $expected, 'Munges restored after i_speed_raw() call');
464}
465
466sub ip_index : Tests(4) {
467  my $test = shift;
468
469  can_ok($test->{info}, 'ip_index');
470
471  my $cache_data = {
472    '_old_ip_index' => 1,
473    '_new_ip_index' => 1,
474    '_new_ip_type'  => 1,
475    'store'        => {
476      'old_ip_index' =>
477        {'2.3.4.5' => 7, '2.2.2.2' => 11},
478      'new_ip_index' =>
479        {'1.4.1.2.3.4' => 6, '1.4.10.255.255.255' => 8, '1.4.8.8.8.8' => 10},
480      'new_ip_type' =>
481        {'1.4.1.2.3.4' => 'unicast', '1.4.10.255.255.255' => 'broadcast', '1.4.8.8.8.8' => 'unicast'},
482    }
483  };
484  $test->{info}->cache($cache_data);
485
486  my $expected = {'2.3.4.5' => 7, '2.2.2.2' => 11};
487
488  cmp_deeply($test->{info}->ip_index(),
489    $expected, q(IP addresses mapped to 'ifIndex' using old 'ipAddrTable'));
490
491  delete $test->{info}{_old_ip_index};
492  $expected = {'1.2.3.4' => 6, '8.8.8.8' => 10};
493
494  cmp_deeply($test->{info}->ip_index(),
495    $expected, q(IP addresses mapped to 'ifIndex' using new 'ipAddressTable'));
496
497  $test->{info}->clear_cache();
498  cmp_deeply($test->{info}->ip_index(), {}, q(No data returns empty hash));
499}
500
501sub ip_table : Tests(4) {
502  my $test = shift;
503
504  can_ok($test->{info}, 'ip_table');
505
506  my $cache_data = {
507    '_old_ip_table' => 1,
508    '_new_ip_index' => 1,
509    '_new_ip_type'  => 1,
510    'store'        => {
511      'old_ip_table' =>
512        {'2.3.4.5' => '2.3.4.5', '2.2.2.2' => '2.2.2.2'},
513      'new_ip_index' =>
514        {'1.4.1.2.3.4' => 6, '1.4.10.255.255.255' => 8, '1.4.8.8.8.8' => 10},
515      'new_ip_type' =>
516        {'1.4.1.2.3.4' => 'unicast', '1.4.10.255.255.255' => 'broadcast', '1.4.8.8.8.8' => 'unicast'},
517    }
518  };
519  $test->{info}->cache($cache_data);
520
521  my $expected = {'2.3.4.5' => '2.3.4.5', '2.2.2.2' => '2.2.2.2'};
522
523  cmp_deeply($test->{info}->ip_table(),
524    $expected, q(IP addresses using old 'ipAddrTable'));
525
526  delete $test->{info}{_old_ip_table};
527  $expected = {'1.2.3.4' => '1.2.3.4', '8.8.8.8' => '8.8.8.8'};
528
529  cmp_deeply($test->{info}->ip_table(),
530    $expected, q(IP addresses using new 'ipAddressTable'));
531
532  $test->{info}->clear_cache();
533  cmp_deeply($test->{info}->ip_table(), {}, q(No data returns empty hash));
534}
535
536sub ip_netmask : Tests(4) {
537  my $test = shift;
538
539  can_ok($test->{info}, 'ip_netmask');
540
541  my $cache_data = {
542    '_old_ip_netmask' => 1,
543    '_new_ip_prefix' => 1,
544    '_new_ip_type'  => 1,
545    'store'        => {
546      'old_ip_netmask' =>
547        {'2.3.4.5' => '255.255.255.0', '2.2.2.2' => '255.255.0.0'},
548      'new_ip_prefix' =>
549        {'1.4.1.2.3.4' => 'IP-MIB::ipAddressPrefixOrigin.2.ipv4."1.2.3.0".24', '1.4.10.2.3.4' => '.1.3.6.1.2.1.4.32.1.5.6.1.4.10.0.0.0.8', '1.4.8.8.8.8' => '.0.0'},
550      'new_ip_type' =>
551        {'1.4.1.2.3.4' => 'unicast', '1.4.10.2.3.4' => 'unicast', '1.4.8.8.8.8' => 'unicast'},
552    }
553  };
554  $test->{info}->cache($cache_data);
555
556  my $expected = {'2.3.4.5' => '255.255.255.0', '2.2.2.2' => '255.255.0.0'};
557
558  cmp_deeply($test->{info}->ip_netmask(),
559    $expected, q(IP netmask using old 'ipAddrTable'));
560
561  delete $test->{info}{_old_ip_netmask};
562  $expected = {'1.2.3.4' => '255.255.255.0', '10.2.3.4' => '255.0.0.0'};
563
564  cmp_deeply($test->{info}->ip_netmask(),
565    $expected, q(IP netmask using new 'ipAddressTable'));
566
567  $test->{info}->clear_cache();
568  cmp_deeply($test->{info}->ip_netmask(), {}, q(No data returns empty hash));
569}
570
571# Topo routines will need to be tested in sub classes for conditionals
572sub has_topo : Tests(2) {
573  my $test = shift;
574
575  can_ok($test->{info}, 'has_topo');
576  is($test->{info}->has_topo(), undef, 'Base class has no topo');
577}
578
579sub get_topo_data : Tests(2) {
580  my $test = shift;
581
582  can_ok($test->{info}, '_get_topo_data');
583  is($test->{info}->_get_topo_data(), undef, 'Base class has no topo data');
584}
585
586sub c_ip : Tests(2) {
587  my $test = shift;
588
589  can_ok($test->{info}, 'c_ip');
590  is($test->{info}->c_ip(), undef, 'Base class has no topo');
591}
592
593sub c_if : Tests(2) {
594  my $test = shift;
595
596  can_ok($test->{info}, 'c_if');
597  is($test->{info}->c_if(), undef, 'Base class has no topo');
598}
599
600sub c_port : Tests(2) {
601  my $test = shift;
602
603  can_ok($test->{info}, 'c_port');
604  is($test->{info}->c_port(), undef, 'Base class has no topo');
605}
606
607sub c_id : Tests(2) {
608  my $test = shift;
609
610  can_ok($test->{info}, 'c_id');
611  is($test->{info}->c_id(), undef, 'Base class has no topo');
612}
613
614sub c_platform : Tests(2) {
615  my $test = shift;
616
617  can_ok($test->{info}, 'c_platform');
618  is($test->{info}->c_platform(), undef, 'Base class has no topo');
619}
620
621sub c_cap : Tests(2) {
622  my $test = shift;
623
624  can_ok($test->{info}, 'c_cap');
625  is($test->{info}->c_cap(), undef, 'Base class has no topo');
626}
627
628# Munges aren't methods, the are functions so calling convention is different
629sub munge_speed : Tests(2) {
630  my $test = shift;
631
632  can_ok($test->{info}, 'munge_speed');
633  is(SNMP::Info::munge_speed('2488000000'),
634    'OC-48', 'Speed munged according to map');
635}
636
637sub munge_highspeed : Tests(6) {
638  my $test = shift;
639
640  can_ok($test->{info}, 'munge_highspeed');
641  is(SNMP::Info::munge_highspeed('15000000'), '15 Tbps', 'Tbps munge');
642  is(SNMP::Info::munge_highspeed('1500000'),
643    '1.5 Tbps', 'Fractional Tbps munge');
644  is(SNMP::Info::munge_highspeed('15000'), '15 Gbps',  'Gbps munge');
645  is(SNMP::Info::munge_highspeed('1500'),  '1.5 Gbps', 'Fractional Gbps munge');
646  is(SNMP::Info::munge_highspeed('100'),   '100 Mbps', 'Mbps munge');
647}
648
649sub munge_ip : Tests(2) {
650  my $test = shift;
651
652  can_ok($test->{info}, 'munge_ip');
653  my $test_ip = pack("C4", split /\./, "123.4.5.6");
654  is(SNMP::Info::munge_ip($test_ip),
655    "123.4.5.6", 'Binary IP to dotted ASCII munge');
656}
657
658sub munge_mac : Tests(4) {
659  my $test = shift;
660
661  can_ok($test->{info}, 'munge_mac');
662
663  # This is how these MACs look in a snmpwalk
664  my $test_mac       = "01 23 45 67 89 AB";
665  my $long_test_mac  = "01 23 45 67 89 AB CD";
666  my $short_test_mac = "01 23 45 67 89";
667
668  # However, they come across the wire as octet string, so we need to pack
669  # them. Before packing, we need to remove the whitespace
670  foreach ($test_mac, $long_test_mac, $short_test_mac) {
671    $_ =~ s/\s//g;
672    $_ = pack("H*", $_);
673  }
674
675  is(SNMP::Info::munge_mac($test_mac),
676    "01:23:45:67:89:ab", 'Octet string to colon separated ASCII hex string');
677  is(SNMP::Info::munge_mac($long_test_mac),
678    undef, 'Too long of an octet string returns undef');
679  is(SNMP::Info::munge_mac($short_test_mac),
680    undef, 'Too short of an octet string returns undef');
681}
682
683sub munge_prio_mac : Tests(4) {
684  my $test = shift;
685
686  can_ok($test->{info}, 'munge_prio_mac');
687
688  # This is how these look in a snmpwalk
689  my $test_mac       = "01 23 45 67 89 AB CD EF";
690  my $long_test_mac  = "01 23 45 67 89 AB CD EF 02";
691  my $short_test_mac = "01 23 45 67 89";
692
693  # However, they come across the wire as octet string, so we need to pack
694  # them. Before packing, we need to remove the whitespace
695  foreach ($test_mac, $long_test_mac, $short_test_mac) {
696    $_ =~ s/\s//g;
697    $_ = pack("H*", $_);
698  }
699
700  is(SNMP::Info::munge_prio_mac($test_mac),
701    "01:23:45:67:89:ab:cd:ef",
702    'Octet string to colon separated ASCII hex string');
703  is(SNMP::Info::munge_prio_mac($long_test_mac),
704    undef, 'Too long of an octet string returns undef');
705  is(SNMP::Info::munge_mac($short_test_mac),
706    undef, 'Too short of an octet string returns undef');
707}
708
709sub munge_prio_port : Tests(4) {
710  my $test = shift;
711
712  can_ok($test->{info}, 'munge_prio_port');
713
714  # This is how these look in a snmpwalk
715  my $test_mac       = "AB CD";
716  my $long_test_mac  = "AB CD EF";
717  my $short_test_mac = "AB";
718
719  # However, they come across the wire as octet string, so we need to pack
720  # them. Before packing, we need to remove the whitespace
721  foreach ($test_mac, $long_test_mac, $short_test_mac) {
722    $_ =~ s/\s//g;
723    $_ = pack("H*", $_);
724  }
725
726  is(SNMP::Info::munge_prio_port($test_mac),
727    "ab:cd", 'Octet string to colon separated ASCII hex string');
728  is(SNMP::Info::munge_prio_port($long_test_mac),
729    undef, 'Too long of an octet string returns undef');
730  is(SNMP::Info::munge_prio_port($short_test_mac),
731    undef, 'Too short of an string returns undef');
732}
733
734# Can't see where this code is actually used, remove?
735sub munge_octet2hex : Tests(2) {
736  my $test = shift;
737
738  can_ok($test->{info}, 'munge_octet2hex');
739
740  # This is how this looks in a snmpwalk
741  my $test_mac = "AB CD";
742
743  # However, is comes across the wire as octet string, so we need to pack
744  # it. Before packing, we need to remove the whitespace
745  $test_mac =~ s/\s//g;
746  $test_mac = pack("H*", $test_mac);
747
748  is(SNMP::Info::munge_octet2hex($test_mac),
749    "abcd", 'Octet string to ASCII hex string');
750}
751
752sub munge_dec2bin : Tests(2) {
753  my $test = shift;
754
755  can_ok($test->{info}, 'munge_dec2bin');
756
757  # This is layers munge, use L3 test case
758  is(SNMP::Info::munge_dec2bin(4), '00000100', 'Binary char to ASCII binary');
759}
760
761sub munge_bits : Tests(2) {
762  my $test = shift;
763
764  can_ok($test->{info}, 'munge_bits');
765
766  my $bits = pack("B*", '00010110');
767
768  is(SNMP::Info::munge_bits($bits),
769    '00010110', 'SNMP2 BITS field to ASCII bit string');
770}
771
772sub munge_counter64 : Tests(4) {
773  my $test = shift;
774
775  my $hc_octets = 744002524365;
776
777  can_ok($test->{info}, 'munge_counter64');
778  is(SNMP::Info::munge_counter64(), undef, 'No arg returns undef');
779
780  # Default is no BigInt
781  is(SNMP::Info::munge_counter64(744002524365),
782    744002524365, 'No BIGINT returns counter');
783
784SKIP: {
785    eval {
786      require Math::BigInt;
787      1;
788    } or do {
789      skip "Math::BigInt not installed", 1;
790    };
791
792    my $class = $test->class;
793    my $sess  = $test->mock_session;
794    my $big_int_info
795      = $class->new('AutoSpecify' => 0, 'BigInt' => 1, 'Session' => $sess,);
796
797    my $obj = SNMP::Info::munge_counter64(744002524365);
798    isa_ok($obj, 'Math::BigInt', 'Test counter64');
799
800  }
801}
802
803sub munge_i_up : Tests(4) {
804  my $test = shift;
805
806  can_ok($test->{info}, 'munge_i_up');
807
808  is(SNMP::Info::munge_i_up(),  undef,            'No arg returns undef');
809  is(SNMP::Info::munge_i_up(4), 'unknown',        'Unknown status');
810  is(SNMP::Info::munge_i_up(7), 'lowerLayerDown', 'Lower layer down status');
811}
812
813sub munge_port_list : Tests(6) {
814  my $test = shift;
815
816  can_ok($test->{info}, 'munge_port_list');
817
818  # Start with the bit string since in a portlist each port is represented as
819  # a bit.
820  # These are typically longer bit strings to cover the all ports in a switch
821  my $bit_string = '01010101010101010101010101010101';
822  my $bits_packed = pack("B*", $bit_string);
823
824  # This is more for documentation than test code. When performing a snmpwalk
825  # the output will typically be a hex string. This converts the packed bit
826  # string above into a hex string as would be seen in the snmpwalk output.
827  my $unpacked_hex_string
828    = join(' ', map { sprintf "%02X", $_ } unpack('C*', $bits_packed));
829
830  # This should be the same as $unpacked_hex_string
831  my $hex_string = '55 55 55 55';
832
833  # Remove the spaces so we can pack, but preserve $hex_string for comparison
834  # testing with $unpacked_hex_string
835  (my $new_hex_string = $hex_string) =~ s/\s//g;
836
837  # Pack the hex string for comparison with $bits_packed
838  my $new_hex_string_packed = pack("H*", $new_hex_string);
839
840  # Finally unpack again to compare with original $bit_string
841  my $new_bit_string = unpack("B*", $new_hex_string_packed);
842
843  # String comparison testing
844  is($unpacked_hex_string, $hex_string,
845    'Unpacking binary bits into hex string is same as expected hex string');
846  is($new_hex_string_packed, $bits_packed,
847    'Packed hex string is equivalent to packed bit string');
848  is($new_bit_string, $bit_string,
849    'Unpacking packed hex string as binary results in original bit string');
850
851  # We are going to get a reference of array of bits back so convert the
852  # string to an array
853  my $expected = [];
854  for my $value (split //, $bit_string) {
855    $expected->[++$#$expected] = $value;
856  }
857  cmp_deeply(SNMP::Info::munge_port_list($bits_packed),
858    $expected, 'Portlist packed bit string coverted to ASCII bit array');
859  cmp_deeply(SNMP::Info::munge_port_list($new_hex_string_packed),
860    $expected, 'Portlist packed hex string coverted to ASCII bit array');
861}
862
863sub munge_null : Tests(2) {
864  my $test = shift;
865
866  can_ok($test->{info}, 'munge_null');
867
868  # See if all possible control characters and nulls are removed
869  my $cntl_string = "Test\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09";
870  $cntl_string .= "This\x0A\x0B\x0C\x0D\x0E\x0F";
871  $cntl_string .= "Crazy\x11\x12\x13\x14\x15\x16\x17\x18\x19";
872  $cntl_string .= "Cntl\x1A\x1B\x1C\x1D\x1E\x1F";
873  $cntl_string .= "\x7FString";
874
875  # This is layers munge, use L3 test case
876  is(SNMP::Info::munge_null($cntl_string),
877    'TestThisCrazyCntlString', 'Null and control characters removed');
878}
879
880sub munge_e_type : Tests(3) {
881  my $test = shift;
882
883  can_ok($test->{info}, 'munge_e_type');
884
885  # This is just calling SNMP::translateOb, so rather than loading another MIB
886  # let's just resolve an OID we don't use from a loaded MIB.
887  is(SNMP::Info::munge_e_type('.1.3.6.1.2.1.11.4'),
888    'snmpInBadCommunityNames', 'OID translated properly');
889
890  # Bogus OID
891  is(SNMP::Info::munge_e_type('.100.3.6.1.2.1.11.4'),
892    '.100.3.6.1.2.1.11.4', 'OID returned when unable to translate');
893}
894
895sub resolve_desthost : Tests(6) {
896  my $test = shift;
897
898  can_ok($test->{info}, 'resolve_desthost');
899
900  is(SNMP::Info::resolve_desthost('1.2.3.4'),
901    '1.2.3.4', 'IPv4 address returns unchanged');
902
903  is(SNMP::Info::resolve_desthost('::1.2.3.4'),
904    'udp6:0:0:0:0:0:0:102:304', q(IPv6 address returns with 'udp6:' prefix));
905
906  is(
907    SNMP::Info::resolve_desthost('udp6:fe80::2d0:b7ff:fe21:c6c0'),
908    'udp6:fe80:0:0:0:2d0:b7ff:fe21:c6c0',
909    q(Net-SNMP example with 'udp6:' prefix returns expected string)
910  );
911
912  is(
913    SNMP::Info::resolve_desthost('fe80::2d0:b7ff:fe21:c6c0'),
914    'udp6:fe80:0:0:0:2d0:b7ff:fe21:c6c0',
915    q(Net-SNMP example IPv6 address returns with 'udp6:' prefix)
916  );
917
918  dies_ok { SNMP::Info::resolve_desthost('1.2.3.4.5') } 'Bad IP dies';
919}
920
921sub init : Tests(3) {
922  my $test = shift;
923
924  can_ok($test->{info}, 'init');
925
926  # When the test info object was created init() was called so all of the
927  # entries in %MIBS should be loaded
928  subtest 'Base MIBs loaded subtest' => sub {
929
930    my $base_mibs = $test->{info}->mibs();
931
932    foreach my $key (keys %$base_mibs) {
933      my $qual_name = "$key" . '::' . "$base_mibs->{$key}";
934      ok(defined $SNMP::MIB{$base_mibs->{$key}}, "$qual_name defined");
935      like(SNMP::translateObj($qual_name),
936        qr/^(\.\d+)+$/, "$qual_name translates to a OID");
937    }
938  };
939
940  # Get SNMP::Version so we can restore
941  my $netsnmp_ver = $SNMP::VERSION;
942  local $SNMP::VERSION = '5.0.1';
943
944  warnings_exist { $test->{info}->init() }
945  [{carped => qr/Net-SNMP\s5.0.1\sseems\sto\sbe\srather\sbuggy/x}],
946    'Use of bad Net-SNMP gives warning';
947
948  $SNMP::VERSION = $netsnmp_ver;
949}
950
951sub args : Tests(2) {
952  my $test = shift;
953
954  # Match args passed to new() in My::Test::Class
955  my $sess = $test->mock_session;
956  my $args = {
957    'AutoSpecify' => 0,
958    'BulkWalk'    => 0,
959    'UseEnums'    => 1,
960    'RetryNoSuch' => 1,
961    'DestHost'    => '127.0.0.1',
962    'Community'   => 'public',
963    'Version'     => 2,
964    'Session'     => $sess,
965    'Debug'       => ($ENV{INFO_TRACE} || 0),
966    'DebugSNMP'   => ($ENV{SNMP_TRACE} || 0),
967  };
968
969  can_ok($test->{info}, 'args');
970  cmp_deeply($test->{info}->args(),
971    $args, 'Args returned match those passed to new()');
972}
973
974# Rename this test to prevent conflicts/recursion within test class
975sub class_call : Tests(2) {
976  my $test  = shift;
977  my $class = $test->class;
978
979  can_ok($test->{info}, 'class');
980  is($test->{info}->class(), $class, 'Class method returns object class');
981}
982
983sub error_throw : Tests(7) {
984  my $test = shift;
985
986  my $error_str = "Test Error String\n";
987
988  can_ok($test->{info}, 'error_throw');
989
990  is($test->{info}->error_throw(), undef, 'No error provided returns undef');
991  is($test->{info}->error(),       undef, '... and no error()');
992  is($test->{info}->error_throw($error_str),
993    undef, 'Error provided returns undef');
994
995  # Since we don't call with no_clear flag the error is cleared
996  is(
997    $test->{info}->error(),
998    "Test Error String\n",
999    '... and error() returns error string of call'
1000  );
1001
1002  # Turn on debug to check carp of error
1003  $test->{info}->debug(1);
1004  warning_is { $test->{info}->error_throw($error_str) }
1005  [{carped => 'Test Error String'}], 'Error carped when debug turned on';
1006  $test->{info}->debug(0);
1007  is(
1008    $test->{info}->error(),
1009    "Test Error String\n",
1010    '... and error() returns error string of call'
1011  );
1012}
1013
1014sub nosuch : Tests(2) {
1015  my $test = shift;
1016
1017  can_ok($test->{info}, 'nosuch');
1018  is($test->{info}->nosuch(), 1, 'RetryNoSuch on by default');
1019}
1020
1021sub session : Tests(4) {
1022  my $test = shift;
1023  my $sess = $test->mock_session;
1024
1025  can_ok($test->{info}, 'session');
1026  cmp_deeply($test->{info}->session(), $sess, 'Session returned');
1027
1028  # This will not be a mocked_session so object type and session will be
1029  # different
1030  my $new_sess = SNMP::Session->new(
1031    DestHost  => '127.0.0.1',
1032    Community => 'new_public',
1033    Version   => 2,
1034  );
1035  cmp_deeply($test->{info}->session($new_sess),
1036    $new_sess, 'New session returned');
1037  isa_ok($test->{info}->session(), 'SNMP::Session', 'New session object');
1038}
1039
1040
1041sub store : Tests(4) {
1042  my $test = shift;
1043
1044  # The store method itself doesn't enforce the naming, so we'll test
1045  # with some totally made up data for 2nd attribute
1046  my $store_data = {
1047    'i_description' =>
1048      {10 => 'Test-Description-10', 20 => 'Test-Description-20'},
1049    'test_attribute' => {Key1 => 'Value 1', Key2 => 'Value 2'}
1050  };
1051
1052  can_ok($test->{info}, 'store');
1053
1054  cmp_deeply($test->{info}->store(), {}, 'Store starts empty');
1055  ok($test->{info}->store($store_data), 'Insert test data into store');
1056  cmp_deeply(
1057    $store_data,
1058    $test->{info}->store(),
1059    'Store method returns test data'
1060  );
1061}
1062
1063sub private_global : Tests(14) {
1064  my $test = shift;
1065
1066  can_ok($test->{info}, '_global');
1067
1068  # This private method and dynamic creation of global methods is covered in
1069  # can() tests. Use these tests to exercise code path for the load_, orig_,
1070  # _raw, as well as, 'NOSUCHOBJECT' and 'NOSUCHINSTANCE' returns so the method
1071  # calls will be indirect.
1072
1073  # Some of these are defined in both SNMPv2-MIB and RFC1213-MIB so use
1074  # OIDs to to make sure no issues with which one was loaded during tests
1075  # Data for load_
1076  my $data = {
1077
1078    # SNMPv2-MIB::sysContact OID = .1.3.6.1.2.1.1.4
1079    '.1.3.6.1.2.1.1.4' => {0 => 'NOSUCHOBJECT'},
1080
1081    # SNMPv2-MIB::sysName OID = .1.3.6.1.2.1.1.5
1082    '.1.3.6.1.2.1.1.5' => {0 => 'NOSUCHINSTANCE'},
1083
1084    # We'll use this to check _raw
1085    # SNMPv2-MIB::sysServices OID = .1.3.6.1.2.1.1.7
1086    '.1.3.6.1.2.1.1.7' => {0 => 64},
1087
1088    # This is a leaf that we don't reference in %GLOBALS
1089    # SNMPv2-MIB::snmpOutTraps OID = .1.3.6.1.2.1.11.29
1090    '.1.3.6.1.2.1.11.29' => {0 => 245},
1091  };
1092
1093  # Lets load cache with data to for initial tests
1094  my $cache_data = {'_layers' => 4, '_name' => 'CacheTestName',};
1095
1096  # Cache expected after running tests
1097  my $expected_cache = {
1098    '_layers'       => 64,
1099    '_snmpOutTraps' => 245,
1100    '_contact'      => undef,
1101    '_name'         => undef,
1102    'store'         => {},
1103  };
1104
1105  # Load the data for use in the mock session
1106  $test->{info}{sess}{Data} = $data;
1107
1108  # Load the cache
1109  $test->{info}->cache($cache_data);
1110
1111  is($test->{info}->name(), 'CacheTestName',
1112    'Call to name() loads cached data');
1113  is($test->{info}->layers(),
1114    '00000100', 'Call to layers() loads cached data and munges');
1115  is($test->{info}->layers_raw(),
1116    4, 'Call to layers_raw() loads cached data without munge');
1117  is($test->{info}->load_layers(),
1118    '01000000', 'Call to load_layers loads new data and munges');
1119  is($test->{info}->layers_raw(),
1120    64, 'Call to layers_raw() loads new data without munge');
1121  is($test->{info}->snmpOutTraps(),
1122    245, 'Call to snmpOutTraps() resolves MIB leaf and returns data');
1123
1124  is($test->{info}->load_contact(),
1125    undef, 'Call to load_contact() returns undef');
1126  is(
1127    $test->{info}->error(),
1128    'SNMP::Info::_global(load_contact) NOSUCHOBJECT',
1129    '... and throws error indicating NOSUCHOBJECT'
1130  );
1131  is($test->{info}->load_name(), undef, 'Call to load_name() returns undef');
1132  is(
1133    $test->{info}->error(),
1134    'SNMP::Info::_global(load_name) NOSUCHINSTANCE',
1135    '... and throws error indicating NOSUCHINSTANCE'
1136  );
1137  cmp_deeply($test->{info}->cache(),
1138    $expected_cache, 'Cache contains expected data');
1139
1140  # Simulate session error, i.e. get fails
1141  $test->{info}{sess}{ErrorStr} = 'Get Failed';
1142
1143  # We need to force load to make it to error
1144  is($test->{info}->load_name(), undef, 'Upon session error returned undef');
1145  is(
1146    $test->{info}->error(),
1147    'SNMP::Info::_global(load_name) Get Failed',
1148    '... and error was thrown'
1149  );
1150
1151  # Clear error or will impact future tests
1152  $test->{info}{sess}{ErrorStr} = undef;
1153}
1154
1155sub private_set : Tests(12) {
1156  my $test = shift;
1157
1158  can_ok($test->{info}, '_set');
1159
1160  # Load cache with data so we can check that _set clears
1161  my $cache_data = {'_name' => 'CacheTestName',};
1162
1163  $test->{info}->cache($cache_data);
1164  is($test->{info}{_name}, 'CacheTestName', 'Cache has a name');
1165
1166  # Simple set
1167  is($test->{info}->_set('name', 'TestName', 0),
1168    1, 'Valid non-array ref name _set() returned 1');
1169  is($test->{info}{_name}, undef, '... and now cache is cleared');
1170
1171  # 4 element array
1172  my $arg_array = ['name', 'TestName', 0];
1173  is($test->{info}->_set($arg_array),
1174    1, 'Valid array reference name _set() returned 1');
1175
1176  # Reference to an array of 4 element arrays, also see set_multi
1177  my $arg_aoa = [['name', 'TestName', 0]];
1178  is($test->{info}->_set($arg_aoa),
1179    1, 'Valid array of arrays reference name _set() returned 1');
1180
1181  # Bogus args
1182  my $bogus_args = {'name' => 'TestName'};
1183  is($test->{info}->_set($bogus_args), undef, 'Invalid args returned undef');
1184  like(
1185    $test->{info}->error(),
1186    qr/SNMP::Info::_set.+-\sFailed/x,
1187    '... and error was thrown'
1188  );
1189
1190  # Bogus attr
1191  is($test->{info}->_set('no_name', 'TestName', 0),
1192    undef, 'Invalid attr returned undef');
1193  like(
1194    $test->{info}->error(),
1195    qr/SNMP::Info::_set.+-\sFailed\sto\sfind/x,
1196    '... and error was thrown'
1197  );
1198
1199  # Simulate session error, i.e. set fails
1200  $test->{info}{sess}{ErrorStr} = 'Set Failed';
1201  is($test->{info}->_set('name', 'TestName', 0),
1202    undef, 'Upon session error returned undef');
1203  is(
1204    $test->{info}->error(),
1205    'SNMP::Info::_set Set Failed',
1206    '... and error was thrown'
1207  );
1208
1209  # Clear error or will impact future tests
1210  $test->{info}{sess}{ErrorStr} = undef;
1211}
1212
1213sub private_make_setter : Tests(10) {
1214  my $test = shift;
1215
1216  # This private method is covered in other tests
1217  can_ok($test->{info}, '_make_setter');
1218
1219  # This private method and dynamic creation of methods is covered in
1220  # can() tests. Use these tests to exercise code path for non-multi SNMP sets
1221  # so the method calls will be indirect. This will indirectly exercise the
1222  # AUTOLOAD and can methods as well.
1223
1224  # Load cache with data so we can check that _set clears
1225  my $cache_data = {'_name' => 'CacheTestName',};
1226
1227  $test->{info}->cache($cache_data);
1228  is($test->{info}{_name}, 'CacheTestName', 'Cache has a name');
1229
1230  # Set on %GLOBALS entry name
1231  is($test->{info}->set_name('TestName'),
1232    1, 'SNMP set on global name with no iid returned 1');
1233  is($test->{info}{_name}, undef, '... and now cache is cleared');
1234
1235  # Same set on the MIB leaf
1236  is($test->{info}->set_sysName('TestName'),
1237    1, 'SNMP set on MIB leaf sysName with no iid returned 1');
1238
1239  # Can provide IID to global if wanted
1240  is($test->{info}->set_name('TestName', 0),
1241    1, 'SNMP set on global name with iid returned 1');
1242
1243  # Set on a %FUNCS table method
1244  is($test->{info}->set_i_alias('TestPortName', 3),
1245    1, 'SNMP set on func i_description with iid returned 1');
1246
1247  # Same set on the table MIB leaf
1248  is($test->{info}->set_ifAlias('TestPortName', 3),
1249    1, 'SNMP set on MIB leaf ifAlias with iid returned 1');
1250
1251  # Simulate session error, i.e. set fails
1252  $test->{info}{sess}{ErrorStr} = 'Set Failed';
1253  is($test->{info}->set_i_alias('TestPortName', 3),
1254    undef, 'Upon session error returned undef');
1255  is(
1256    $test->{info}->error(),
1257    'SNMP::Info::_set Set Failed',
1258    '... and error was thrown'
1259  );
1260
1261  # Clear error or will impact future tests
1262  $test->{info}{sess}{ErrorStr} = undef;
1263}
1264
1265sub set_multi : Tests(3) {
1266  my $test = shift;
1267
1268  can_ok($test->{info}, 'set_multi');
1269
1270 # This is contrived and mock set always returns true, so this test case
1271 # could be improved. The multi_set method is meant for use similar to a
1272 # database transaction where all sets must be completed in one atomic operation
1273  my $multi_set = [
1274    ['i_up_admin',    2,            3],
1275    ['i_description', 'Port Down',  3],
1276    ['ifDescr',       'Test Descr', 4],
1277  ];
1278
1279  is($test->{info}->set_multi($multi_set), 1, 'Valid multi_set() returned 1');
1280
1281  $multi_set = [
1282    ['i_up_admin',    2,           3],
1283    ['i_description', 'Port Down', 3],
1284    ['bogus_set',     'What?',     4],
1285  ];
1286
1287  is($test->{info}->set_multi($multi_set),
1288    undef, 'Invalid multi_set() returned undef');
1289
1290}
1291
1292sub load_all : Tests(6) {
1293  my $test = shift;
1294
1295  can_ok($test->{info}, 'load_all');
1296
1297  # This method uses load_ and will not utilize the cache so we need to define
1298  # data for the mocked session's get/getnext methods
1299
1300  # Use OIDs to prevent resolution conflicts of fully qualified names between
1301  # RFC1213-MIB and IF-MIB dependant upon which was loaded first via random
1302  # hash ordering. We only need a subset of data to verify the method is
1303  # working, no need to have data for all funcs the method calls. Use a subset
1304  # of the IF-MIB::ifTable and IF-MIB::ifXTable
1305  my $data = {
1306    '.1.3.6.1.2.1.2.2.1.1' => {1 => 1, 2 => 2, 3 => 3,},
1307    '.1.3.6.1.2.1.2.2.1.2' => {
1308      1 => 'Loopback',
1309      2 => '1/3/1, 10/100 Ethernet TX',
1310      3 => '1/3/2, 10/100 Ethernet TX'
1311    },
1312    '.1.3.6.1.2.1.2.2.1.3'     => {1 => 24,   2 => 6,         3 => 6},
1313    '.1.3.6.1.2.1.2.2.1.4'     => {1 => 1500, 2 => 1514,      3 => 1514},
1314    '.1.3.6.1.2.1.2.2.1.5'     => {1 => 0,    2 => 100000000, 3 => 1000000000,},
1315    '.1.3.6.1.2.1.31.1.1.1.15' => {1 => 0,    2 => 100,       3 => 1000,},
1316  };
1317
1318  # Data is stored unmunged, OID's will be resolved and cache entries stored
1319  # under %FUNCS names
1320  my $expected_data = {
1321    'i_index'       => {1 => 1, 2 => 2, 3 => 3,},
1322    'i_description' => {
1323      1 => 'Loopback',
1324      2 => '1/3/1, 10/100 Ethernet TX',
1325      3 => '1/3/2, 10/100 Ethernet TX'
1326    },
1327    'i_type'       => {1 => 24,   2 => 6,         3 => 6},
1328    'i_mtu'        => {1 => 1500, 2 => 1514,      3 => 1514},
1329    'i_speed'      => {1 => 0,    2 => 100000000, 3 => 1000000000,},
1330    'i_speed_high' => {1 => 0,    2 => 100,       3 => 1000,},
1331
1332    # In base class defined as ifIndex
1333    'interfaces' => {1 => 1, 2 => 2, 3 => 3,},
1334  };
1335
1336  # Start with some data in store to verify it is overwritten
1337  my $store_data
1338    = {'i_description' =>
1339      {10 => 'Test-Description-10', 20 => 'Test-Description-20'},
1340    };
1341
1342  cmp_deeply($test->{info}->store(), {}, 'Store starts empty');
1343  ok($test->{info}->store($store_data), 'Insert test data into store');
1344  cmp_deeply($test->{info}->store(), $store_data,
1345    '... store now has test data');
1346
1347  # Load the data for use in the mock session
1348  $test->{info}{sess}{Data} = $data;
1349
1350  cmp_deeply($test->{info}->load_all(),
1351    $expected_data, 'Call to load_all() returns expected data');
1352  cmp_deeply($test->{info}->store(),
1353    $expected_data, '... and store now has expected data from load_all()');
1354}
1355
1356# Need to rename from all to prevent name conflict
1357sub my_all : Tests(9) {
1358  my $test = shift;
1359
1360  can_ok($test->{info}, 'all');
1361
1362  # Use OIDs to prevent resolution conflicts of fully qualified names between
1363  # RFC1213-MIB and IF-MIB dependant upon which was loaded first via random
1364  # hash ordering. We only need a bare minimum of data to verify the method is
1365  # working since it relies on load_all() which has its own tests.
1366  my $data = {
1367    '.1.3.6.1.2.1.2.2.1.2' => {
1368      1 => 'Loopback',
1369      2 => '1/3/1, 10/100 Ethernet TX',
1370      3 => '1/3/2, 10/100 Ethernet TX'
1371    },
1372  };
1373
1374  # Data is stored unmunged, OID's will be resolved and cache entries stored
1375  # under %FUNCS names
1376  my $expected_data = {
1377    'i_description' => {
1378      1 => 'Loopback',
1379      2 => '1/3/1, 10/100 Ethernet TX',
1380      3 => '1/3/2, 10/100 Ethernet TX'
1381    },
1382  };
1383
1384  # Start with some data in store to verify it is overwritten on first call
1385  # and whatever is in store() is returned on subsequent calls to all
1386  my $store_data
1387    = {'i_description' =>
1388      {10 => 'Test-Description-10', 20 => 'Test-Description-20'},
1389    };
1390
1391  cmp_deeply($test->{info}->store(), {}, 'Store starts empty');
1392  ok($test->{info}->store($store_data), 'Insert test data into store');
1393  cmp_deeply($test->{info}->store(), $store_data,
1394    '... store now has test data');
1395
1396  # Load the data for use in the mock session
1397  $test->{info}{sess}{Data} = $data;
1398
1399  cmp_deeply($test->{info}->all(),
1400    $expected_data, 'Call to all() returns expected data');
1401  cmp_deeply($test->{info}->store(),
1402    $expected_data, '... and store now has expected data from all()');
1403
1404  ok($test->{info}->store($store_data), 'Re-insert test data into store');
1405  cmp_deeply($test->{info}->store(),
1406    $store_data, '... store again has test data');
1407
1408  cmp_deeply($test->{info}->all(),
1409    $expected_data,
1410    '... call to all() returns test data, no call to load_all()');
1411}
1412
1413sub private_load_attr : Tests(18) {
1414  my $test = shift;
1415
1416  can_ok($test->{info}, '_load_attr');
1417
1418  # This private method and dynamic creation of table aka func methods is
1419  # covered in can() tests. Use these tests to exercise code path for
1420  # the load_, orig_, _raw, as well as, 'NOSUCHOBJECT', 'NOSUCHINSTANCE',
1421  # and 'ENDOFMIBVIEW' returns so the method calls will be indirect.
1422
1423  # Currently mocked session in test harness doesn't support bulkwalk, so
1424  # that code path is not tested
1425
1426  # Some of these are defined in both SNMPv2-MIB and RFC1213-MIB so use
1427  # OIDs to to make sure no issues with which one was loaded during tests
1428  # Data for load_
1429  my $data = {
1430    '.1.3.6.1.2.1.2.2.1.2' => {
1431      1 => 'Loopback',
1432      2 => '1/3/1, 10/100 Ethernet TX',
1433      3 => '1/3/2, 10/100 Ethernet TX'
1434    },
1435    '.1.3.6.1.2.1.2.2.1.8'      => {1 => 4,       2 => 'up',   3 => 7},
1436    'IF-MIB::ifPromiscuousMode' => {1 => 'false', 2 => 'true', 3 => 'false'},
1437    'IF-MIB::ifConnectorPresent'         => {0 => 'NOSUCHOBJECT'},
1438    'IF-MIB::ifCounterDiscontinuityTime' => {0 => 'NOSUCHINSTANCE'},
1439    'IF-MIB::ifHCOutOctets' =>
1440      {1 => 0, 2 => 1828306359704, 3 => 1002545943585, 4 => 'ENDOFMIBVIEW'},
1441
1442    # Tables to test partial and full OIDs
1443    '.1.3.6.1.4.1.171.12.1.1.12'   => {1 => 'partial', 2 => 'oid', 3 => 'data'},
1444    '.100.3.6.1.4.1.171.12.1.1.12' => {2 => 'full',    3 => 'oid', 4 => 'leaf'},
1445  };
1446
1447  # Load cache with data to for initial tests
1448  my $cache_data = {
1449    '_i_description' => 1,
1450    '_i_up'          => 1,
1451    'store'          => {
1452      'i_description' =>
1453        {10 => 'Test-Description-10', 20 => 'Test-Description-20'},
1454      'i_up' => {10 => 6, 20 => 7}
1455    }
1456  };
1457
1458  # Cache expected after running tests
1459  # Note: i_up starts in cache and call to load_i_up for new data increments
1460  # the cache counter
1461  # Note: We don't call load_i_description so the data from cache never gets
1462  # replaced
1463  # Note: munge_i_up only munges integers 4-7 as 1-3 are already enumerated
1464  my $expected_cache = {
1465    '_i_description'              => 1,
1466    '_i_up'                       => 2,
1467    '_ifPromiscuousMode'          => 1,
1468    '_ifConnectorPresent'         => undef,
1469    '_ifCounterDiscontinuityTime' => undef,
1470    '_i_octet_out64'              => 1,
1471    'store'                       => {
1472      'i_description' =>
1473        {10 => 'Test-Description-10', 20 => 'Test-Description-20'},
1474      'i_up'              => {1 => 4,       2 => 'up',   3 => 7},
1475      'ifPromiscuousMode' => {1 => 'false', 2 => 'true', 3 => 'false'},
1476      'i_octet_out64' => {1 => 0, 2 => 1828306359704, 3 => 1002545943585}
1477    }
1478  };
1479
1480  my $expected_cache_munge_iup = {10 => 'notPresent', 20 => 'lowerLayerDown'};
1481  my $expected_load_munge_iup
1482    = {1 => 'unknown', 2 => 'up', 3 => 'lowerLayerDown'};
1483
1484  my $expected_load_raw_iftype = {1 => 24, 2 => 6, 3 => 6};
1485
1486  # Load the data for use in the mock session
1487  $test->{info}{sess}{Data} = $data;
1488
1489  # Load the cache
1490  $test->{info}->cache($cache_data);
1491
1492  cmp_deeply(
1493    $test->{info}->i_description(),
1494    $cache_data->{'store'}{'i_description'},
1495    'Call to i_description() loads cached data'
1496  );
1497  cmp_deeply($test->{info}->i_up(),
1498    $expected_cache_munge_iup, 'Call to i_up() loads cached data and munges');
1499  cmp_deeply(
1500    $test->{info}->i_up_raw(),
1501    $cache_data->{'store'}{'i_up'},
1502    'Call to i_up_raw() loads cached data without munge'
1503  );
1504  cmp_deeply($test->{info}->load_i_up(),
1505    $expected_load_munge_iup, 'Call to load_i_up() loads new data and munges');
1506  cmp_deeply(
1507    $test->{info}->i_up_raw(),
1508    $expected_cache->{'store'}{'i_up'},
1509    'Call to i_up_raw() loads new data without munge'
1510  );
1511
1512  # Test ability to use MIB leaf
1513  cmp_deeply(
1514    $test->{info}->ifPromiscuousMode(),
1515    $data->{'IF-MIB::ifPromiscuousMode'},
1516    'Call to ifPromiscuousMode() resolves MIB leaf and returns data'
1517  );
1518
1519  # Test error conditions
1520  is($test->{info}->load_ifConnectorPresent(),
1521    undef, 'Call to load_ifConnectorPresent() returns undef');
1522  is(
1523    $test->{info}->error(),
1524    'SNMP::Info::_load_attr: load_ifConnectorPresent :  NOSUCHOBJECT',
1525    '... and throws error indicating NOSUCHOBJECT'
1526  );
1527  is($test->{info}->load_ifCounterDiscontinuityTime(),
1528    undef, 'Call to load_ifCounterDiscontinuityTime() returns undef');
1529  is(
1530    $test->{info}->error(),
1531    'SNMP::Info::_load_attr: load_ifCounterDiscontinuityTime :  NOSUCHINSTANCE',
1532    '... and throws error indicating NOSUCHINSTANCE'
1533  );
1534
1535  # 'ENDOFMIBVIEW' isn't an error condition, it just stops the walk
1536  # Ask for raw since don't want munge_counter64 to turn results into objects
1537  # and want to compare to what will be stored in cache at the end
1538  cmp_deeply(
1539    $test->{info}->i_octet_out64_raw(),
1540    $expected_cache->{'store'}{'i_octet_out64'},
1541    'Call to i_up_raw() loads new data without munge'
1542  );
1543
1544  # Test partial fetches
1545  cmp_deeply(
1546    $test->{info}->i_octet_out64_raw(3),
1547    +{3 => 1002545943585},
1548    'Partial call to i_octet_out64_raw(3) data without munge'
1549  );
1550  cmp_deeply(
1551    $test->{info}->i_description(2),
1552    +{2 => '1/3/1, 10/100 Ethernet TX'},
1553    'Partial call to i_description(2) loads new data'
1554  );
1555  ok(!exists $test->{info}{store}{i_description}{2},
1556    '... and does not store it in cache');
1557
1558  cmp_deeply($test->{info}->cache(),
1559    $expected_cache, 'Cache contains expected data');
1560
1561  # Test OID based table fetches
1562  # This is from Layer3::DLink will only partially resolve
1563  $test->{info}{funcs}{partial_oid} = '.1.3.6.1.4.1.171.12.1.1.12';
1564
1565  my $expected_p_oid_data = {1 => 'partial', 2 => 'oid', 3 => 'data'};
1566
1567  cmp_deeply($test->{info}->partial_oid(),
1568    $expected_p_oid_data, 'Partial translated OID leaf returns expected data');
1569
1570  # This is a bogus OID will not translate at all
1571  $test->{info}{funcs}{full_oid} = '.100.3.6.1.4.1.171.12.1.1.12';
1572
1573  my $expected_f_oid_data = {2 => 'full', 3 => 'oid', 4 => 'leaf'};
1574
1575  cmp_deeply($test->{info}->full_oid(),
1576    $expected_f_oid_data, 'Full OID leaf returns expected data');
1577}
1578
1579sub private_show_attr : Tests(3) {
1580  my $test = shift;
1581
1582  can_ok($test->{info}, '_show_attr');
1583
1584  # Load cache with data
1585  my $cache_data = {'_i_up' => 1, 'store' => {'i_up' => {10 => 6, 20 => 7}}};
1586
1587  # Load the cache
1588  $test->{info}->cache($cache_data);
1589
1590  my $expected_munge = {10 => 'notPresent', 20 => 'lowerLayerDown'};
1591
1592  # Minimal tests as this method is heavily covered in other testing
1593  cmp_deeply($test->{info}->_show_attr('i_up'),
1594    $expected_munge, 'Shows munged data from cache without raw flag');
1595  cmp_deeply(
1596    $test->{info}->_show_attr('i_up', 1),
1597    $cache_data->{'store'}{'i_up'},
1598    'Shows unmunged data from cache with raw flag'
1599  );
1600}
1601
1602sub snmp_connect_ip : Tests(3) {
1603  my $test = shift;
1604
1605  can_ok($test->{info}, 'snmp_connect_ip');
1606
1607  is($test->{info}->snmp_connect_ip('127.0.0.1', 2, 'public'),
1608    undef, 'Connect to loopback returns undef');
1609  is($test->{info}->snmp_connect_ip('0.0.0.0', 2, 'public'),
1610    undef, 'Connect to zeros returns undef');
1611
1612  # Live call moved to 10_remote_snmplabs.t
1613}
1614
1615sub modify_port_list : Tests(4) {
1616  my $test = shift;
1617
1618  can_ok($test->{info}, 'modify_port_list');
1619
1620  # Let munge_port_list do the work of converting this into the portlist array
1621  my $orig_plist = SNMP::Info::munge_port_list(pack("B*", '01010101'));
1622  my $new_plist_1    = pack("B*", '01110101');
1623  my $new_plist_0    = pack("B*", '01110001');
1624  my $expanded_plist = pack("B*", '0111000100000001');
1625
1626  # This call will actually modify $orig_plist
1627  is($test->{info}->modify_port_list($orig_plist, 2, 1),
1628    $new_plist_1, 'Bit in offset position 2 changed to on');
1629
1630  # Here we start with modified $orig_plist, now '01110101'
1631  is($test->{info}->modify_port_list($orig_plist, 5, 0),
1632    $new_plist_0, 'Bit in offset position 5 changed to off');
1633
1634  # Modified $orig_plist, now '01110001'
1635  is($test->{info}->modify_port_list($orig_plist, 15, 1),
1636    $expanded_plist,
1637    'Bit in offset position 15 changed to on and portlist array expanded');
1638}
1639
1640sub private_cache : Tests(1) {
1641  my $test = shift;
1642
1643  # This method is covered in private_global and private_load_attr
1644  # tests so just cover with can() here
1645  can_ok($test->{info}, '_cache');
1646}
1647
1648sub private_munge : Tests(1) {
1649  my $test = shift;
1650
1651  # This method is covered in private_global and private_load_attr
1652  # tests so just cover with can() here
1653  can_ok($test->{info}, '_munge');
1654}
1655
1656sub private_validate_autoload_method : Tests(8) {
1657  my $test = shift;
1658
1659  can_ok($test->{info}, '_validate_autoload_method');
1660
1661  subtest '%GLOBALS _validate_autoload_method() subtest' => sub {
1662
1663    foreach my $prefix ('', 'load_', 'orig_') {
1664      my $test_global = "$prefix" . 'contact';
1665      cmp_deeply(
1666        $test->{info}->_validate_autoload_method("$test_global"),
1667        ['.1.3.6.1.2.1.1.4.0', 0],
1668        qq(Global '$test_global' validates)
1669      );
1670    }
1671    cmp_deeply(
1672      $test->{info}->_validate_autoload_method('set_contact'),
1673      ['.1.3.6.1.2.1.1.4', 0],
1674      q(Global 'set_contact' validates)
1675    );
1676    cmp_deeply(
1677      $test->{info}->_validate_autoload_method('contact_raw'),
1678      ['.1.3.6.1.2.1.1.4.0', 0],
1679      q(Global 'contact_raw' validates)
1680    );
1681  };
1682
1683  # Use a leaf we don't have defined in %GLOBALS that is read/write to pass
1684  # all tests, we'll test access separately
1685  subtest 'Single instance MIB leaf _validate_autoload_method() subtest' =>
1686    sub {
1687
1688    foreach my $prefix ('', 'load_', 'orig_') {
1689      my $test_global = "$prefix" . 'snmpEnableAuthenTraps';
1690      cmp_deeply(
1691        $test->{info}->_validate_autoload_method("$test_global"),
1692        ['.1.3.6.1.2.1.11.30.0', 0],
1693        qq(MIB leaf '$test_global' validates)
1694      );
1695    }
1696    cmp_deeply(
1697      $test->{info}->_validate_autoload_method('set_snmpEnableAuthenTraps'),
1698      ['.1.3.6.1.2.1.11.30', 0],
1699      q(MIB leaf 'set_snmpEnableAuthenTraps' validates)
1700    );
1701    cmp_deeply(
1702      $test->{info}->_validate_autoload_method('snmpEnableAuthenTraps_raw'),
1703      ['.1.3.6.1.2.1.11.30.0', 0],
1704      q(MIB leaf 'snmpEnableAuthenTraps_raw' validates)
1705    );
1706    };
1707
1708  subtest '%FUNCS _validate_autoload_method() subtest' => sub {
1709
1710    foreach my $prefix ('', 'load_', 'orig_') {
1711      my $test_global = "$prefix" . 'i_alias';
1712      cmp_deeply(
1713        $test->{info}->_validate_autoload_method("$test_global"),
1714        ['.1.3.6.1.2.1.31.1.1.1.18', 1],
1715        qq(Func '$test_global' validates)
1716      );
1717    }
1718    cmp_deeply(
1719      $test->{info}->_validate_autoload_method('set_i_alias'),
1720      ['.1.3.6.1.2.1.31.1.1.1.18', 1],
1721      q(Func 'set_i_alias' validates)
1722    );
1723    cmp_deeply(
1724      $test->{info}->_validate_autoload_method('i_alias_raw'),
1725      ['.1.3.6.1.2.1.31.1.1.1.18', 1],
1726      q(Func 'i_alias_raw' validates)
1727    );
1728  };
1729
1730  # Use a leaf we don't have defined in %FUNCS that is read/write to pass
1731  # all tests, we'll test access separately
1732  subtest 'Table MIB leaf _validate_autoload_method() subtest' => sub {
1733
1734    foreach my $prefix ('', 'load_', 'orig_') {
1735      my $test_global = "$prefix" . 'ifPromiscuousMode';
1736      cmp_deeply(
1737        $test->{info}->_validate_autoload_method("$test_global"),
1738        ['.1.3.6.1.2.1.31.1.1.1.16', 1],
1739        qq(Func '$test_global' validates)
1740      );
1741    }
1742    cmp_deeply(
1743      $test->{info}->_validate_autoload_method('set_ifPromiscuousMode'),
1744      ['.1.3.6.1.2.1.31.1.1.1.16', 1],
1745      q(Func 'set_ifPromiscuousMode' validates)
1746    );
1747    cmp_deeply(
1748      $test->{info}->_validate_autoload_method('ifPromiscuousMode_raw'),
1749      ['.1.3.6.1.2.1.31.1.1.1.16', 1],
1750      q(Func 'ifPromiscuousMode_raw' validates)
1751    );
1752  };
1753
1754  is($test->{info}->_validate_autoload_method('ifStackHigherLayer'),
1755    undef, q(MIB leaf 'ifStackHigherLayer' not-accessible, returns undef));
1756
1757  # Test that read-only leaf won't validate set_
1758  is($test->{info}->_validate_autoload_method('set_i_lastchange'),
1759    undef,
1760    q(Func 'i_lastchange' is read-only, 'set_i_lastchange' returns undef));
1761
1762  # Check fully qualified MIB leaf w substitutions validates
1763  cmp_deeply(
1764    $test->{info}->_validate_autoload_method('IF_MIB__ifConnectorPresent'),
1765    ['.1.3.6.1.2.1.31.1.1.1.17', 1],
1766    q(Fully qualified 'IF_MIB__ifConnectorPresent' validates)
1767  );
1768}
1769
1770# Prefix with private as we don't want to accidentally override in test class
1771sub private_can : Tests(9) {
1772  my $test = shift;
1773
1774  # This method is heavily covered across tests, just verify here that
1775  # a successful call places the method in the symbol table
1776
1777  # See perldoc Symbol, specifically Symbol::delete_package. We can't assume
1778  # symbols are deleted with the object between tests. Since we can() all
1779  # globals and funcs during tests use MIB leafs to test table and scalar
1780  # methods. The symbol_test() method is defined in My::Test::Class
1781  # Note: if these tests start to fail make sure we aren't using the leaf
1782  # in other tests
1783
1784  # This leaf tests the global path SNMPv2-MIB::snmpInBadCommunityNames
1785  is($test->symbol_test('snmpInBadCommunityNames'),
1786    0, q(Method 'snmpInBadCommunityNames' is not defined in the symbol table));
1787  can_ok($test->{info}, 'snmpInBadCommunityNames');
1788  is($test->symbol_test('snmpInBadCommunityNames'),
1789    1, q(Method 'snmpInBadCommunityNames' is now defined in the symbol table));
1790
1791  # This leaf tests the table path IF-MIB::ifCounterDiscontinuityTime
1792  is($test->symbol_test('ifCounterDiscontinuityTime'),
1793    0,
1794    q(Method 'ifCounterDiscontinuityTime' is not defined in the symbol table));
1795  can_ok($test->{info}, 'ifCounterDiscontinuityTime');
1796  is($test->symbol_test('ifCounterDiscontinuityTime'),
1797    1,
1798    q(Method 'ifCounterDiscontinuityTime' is now defined in the symbol table));
1799
1800  # This leaf tests the set_ path SNMPv2-MIB::snmpSetSerialNo
1801  is($test->symbol_test('set_snmpSetSerialNo'),
1802    0, q(Method 'set_snmpSetSerialNo' is not defined in the symbol table));
1803  can_ok($test->{info}, 'set_snmpSetSerialNo');
1804  is($test->symbol_test('set_snmpSetSerialNo'),
1805    1, q(Method 'set_snmpSetSerialNo' is now defined in the symbol table));
1806}
1807
1808# Prefix with private as we don't want to accidentally override in test class
1809sub private_autoload : Tests(3) {
1810  my $test = shift;
1811
1812  # This method is covered in other tests, just verify here that
1813  # a successful call places the method in the symbol table. Since can() does
1814  # the majority of the work call a method without calling can() first. Same
1815  # as noted in the private_can test we need to use a leaf not used elsewhere
1816  # (IP-MIB::ipDefaultTTL) to make sure method isn't in the symbol table first.
1817  # AUTOLOAD calls the method after inserted in the symbol table, so we
1818  # populate cache to return some data
1819
1820  # Load the cache
1821  my $cache_data = {'_ipDefaultTTL' => 64};
1822  $test->{info}->cache($cache_data);
1823
1824  is($test->symbol_test('ipDefaultTTL'),
1825    0, q(Method 'ipDefaultTTL' is not defined in the symbol table));
1826  is($test->{info}->ipDefaultTTL(),
1827    64, q(Method 'ipDefaultTTL' called and returned expected data'));
1828  is($test->symbol_test('ipDefaultTTL'),
1829    1, q(Method 'ipDefaultTTL' is now defined in the symbol table));
1830}
1831
1832# Prefix with private as we don't want to accidentally override in test class
1833sub private_destroy : Tests(2) {
1834  my $test = shift;
1835
1836  can_ok($test->{info}, 'DESTROY');
1837  is($test->{info}->DESTROY(), undef, 'DESTROY returns undef');
1838}
1839
18401;
1841