diff --git a/xt/lib/My/Test/Class.pm b/xt/lib/My/Test/Class.pm index 2e1300c5..d7c6b53e 100644 --- a/xt/lib/My/Test/Class.pm +++ b/xt/lib/My/Test/Class.pm @@ -58,11 +58,26 @@ sub setup : Tests(setup) { my $class = $test->class; my $sess = $test->mock_session; - $test->{info} - = $class->new('AutoSpecify' => 0, 'BulkWalk' => 0, 'Session' => $sess,); + $test->{info} = $class->new( + 'AutoSpecify' => 0, + 'BulkWalk' => 0, + 'UseEnums' => 1, + 'RetryNoSuch' => 1, + 'DestHost' => '127.0.0.1', + 'Community' => 'public', + 'Version' => 2, + 'Session' => $sess, + ); } -sub teardown : Tests(teardown) { my $test = shift; $test->{info} = undef; } +sub teardown : Tests(teardown) { + my $test = shift; + my $sess = $test->mock_session; + + # Make sure we start clear object and any mocked session data after each test + $test->{info} = undef; + $sess->{Data} = {}; +} sub create_mock_session { @@ -137,10 +152,14 @@ sub mock_get { ($leaf, $iid) = $oid_name =~ /^(\S+::\w+)[.]?(\S+)*$/x; } + # This is a lot of indirection, but we need the base OID, it may be + # passed with a zero for non table leaf + my $oid_base = SNMP::translateObj($leaf); + $iid ||= 0; my $new_iid = $iid; my $val = $EMPTY; - my $data = $c_data->{$leaf} || {}; + my $data = $c_data->{$leaf} || $c_data->{$oid_base} || {}; my $count = scalar keys %{$data} || 0; if ($count > 1) { my $found = 0; @@ -233,7 +252,17 @@ sub mock_getnext { } # For testing purposes assume sets worked -sub mock_set {1} +sub mock_set { + my $mock_session = shift; + + $mock_session->mock( + 'set', + sub { + return 1; + } + ); + return; +} # Utility to load snmpwalk from a file to use for mock sessions sub load_snmpdata { @@ -257,15 +286,20 @@ sub load_snmpdata { return $snmp_data; } -# Grab the symbol table for verification that -# dynamic methods via AUTOLOAD and can() have been inserted -sub symbols { - my $test = shift; - my $class = $test->class; +# Returns 1 if the method is defined in the symbol table 0 otherwise, used for +# verification that dynamic methods via AUTOLOAD and can() have been inserted +# into the symbol table +sub symbol_test { + my $test = shift; + my $method = shift; + + my $class = $test->class; + my %symbols = (); { no strict 'refs'; ## no critic (ProhibitNoStrict) - return \%{$class . '::'}; + %symbols = %{$class . '::'}; } + return (defined($symbols{$method}) ? 1 : 0); } 1; diff --git a/xt/lib/SNMP/Info/Test.pm b/xt/lib/SNMP/Info/Test.pm index 4df66c6e..13ac82c5 100644 --- a/xt/lib/SNMP/Info/Test.pm +++ b/xt/lib/SNMP/Info/Test.pm @@ -33,7 +33,7 @@ use Test::Class::Most parent => 'My::Test::Class'; use SNMP::Info; -sub _constructor : Tests(11) { +sub constructor : Tests(11) { my $test = shift; my $class = $test->class; my $sess = $test->mock_session; @@ -41,83 +41,62 @@ sub _constructor : Tests(11) { can_ok $class, 'new'; isa_ok $test->{info}, $class, '... and the object it returns'; - is(defined $test->{info}{init}, 1, 'mibs initialized'); + is(defined $test->{info}{init}, 1, 'MIBs initialized'); ok( scalar keys %{$test->{info}{mibs}}, - 'mibs subclass data structure initialized' + 'MIBs subclass data structure initialized' ); ok( scalar keys %{$test->{info}{globals}}, - 'globals subclass data structure initialized' + 'Globals subclass data structure initialized' ); ok( scalar keys %{$test->{info}{funcs}}, - 'funcs subclass data structure initialized' + 'Funcs subclass data structure initialized' ); ok( scalar keys %{$test->{info}{munge}}, - 'munge subclass data structure initialized' + 'Munge subclass data structure initialized' ); - is_deeply($test->{info}{store}, {}, 'store initialized'); + is_deeply($test->{info}{store}, {}, 'Store initialized'); - is($test->{info}{snmp_comm}, 'public', 'snmp comm arg saved'); - is($test->{info}{snmp_ver}, 2, 'snmp version arg saved'); - is($test->{info}{snmp_user}, 'initial', 'snmp user arg saved'); -} - -sub globals : Tests(2) { - my $test = shift; - - can_ok($test->{info}, 'globals'); - - subtest 'Globals can() subtest' => sub { - - my $test_globals = $test->{info}->globals; - foreach my $key (keys %$test_globals) { - can_ok($test->{info}, $key); - } - }; -} - -sub funcs : Tests(2) { - my $test = shift; - - can_ok($test->{info}, 'funcs'); - - subtest 'Funcs can() subtest' => sub { - - my $test_funcs = $test->{info}->funcs; - foreach my $key (keys %$test_funcs) { - can_ok($test->{info}, $key); - } - }; + is($test->{info}{snmp_comm}, 'public', 'SNMP comm arg saved'); + is($test->{info}{snmp_ver}, 2, 'SNMP version arg saved'); + is($test->{info}{snmp_user}, 'initial', 'SNMP user arg saved'); } # update() needs to be reworked to discard all args except community # or context as described in documentation -sub update : Tests(4) { +sub update : Tests(9) { my $test = shift; + can_ok($test->{info}, 'update'); + # Starting community - is($test->{info}{sess}{Community}, 'public', 'original community'); + is($test->{info}{sess}{Community}, 'public', q(Original community 'public')); -TODO: { - todo_skip "CI issues with update() tests", 3 if 1; + # Starting context + ok(!exists $test->{info}{sess}{Context}, q(Context doesn't exist)); - # Change community - my %update_args = ('Community' => 'new_community',); - $test->{info}->update(%update_args); - is($test->{info}{sess}{Community}, 'new_community', 'community changed'); +#TODO: { +# todo_skip "CI issues with update() tests", 6 if 1; - # Starting context - is($test->{info}{sess}{Context}, '', 'original context'); + # Change community + my %update_args = ('Community' => 'new_community',); + delete $test->{info}{args}{Session}; + ok($test->{info}->update(%update_args), 'Update community'); + is($test->{info}->error(), undef, '... and no error'); + is($test->{info}{sess}{Community}, 'new_community', 'Community changed'); - # Change context - %update_args = ('Context' => 'new_context',); - $test->{info}->update(%update_args); - is($test->{info}->{sess}->{Context}, 'new_context', 'context changed'); - } + # Change context + $test->{info}{args}{Version} = 3; + %update_args = ('Context' => 'new_context',); + ok($test->{info}->update(%update_args), 'Update Context'); + is($test->{info}->error(), undef, '... and no error'); + is($test->{info}{sess}{Context}, 'new_context', 'Context changed'); + +# } } sub cache_and_clear_cache : Tests(9) { @@ -139,26 +118,26 @@ sub cache_and_clear_cache : Tests(9) { my $empty_cache = {'store' => {}}; can_ok($test->{info}, 'cache'); - cmp_deeply($empty_cache, $test->{info}->cache(), 'cache starts empty'); - ok($test->{info}->cache($cache_data), 'insert test data into cache'); + cmp_deeply($empty_cache, $test->{info}->cache(), 'Cache starts empty'); + ok($test->{info}->cache($cache_data), 'Insert test data into cache'); cmp_deeply( $cache_data, $test->{info}->cache(), - 'cache method returns test data' + 'Cache method returns test data' ); is($test->{info}->name(), - 'Test-Name', 'global method call returned cached data'); + 'Test-Name', 'Global method call returned cached data'); cmp_deeply( $test->{info}->i_description(), $cache_data->{store}{i_description}, - 'funcs method call returned cached data' + 'Funcs method call returned cached data' ); can_ok($test->{info}, 'clear_cache'); ok($test->{info}->clear_cache(), 'cache cleared'); cmp_deeply( $empty_cache, $test->{info}->cache(), - 'no cached data returned after clear_cache method call' + 'No cached data returned after clear_cache method call' ); } @@ -171,12 +150,12 @@ sub debug : Tests(4) { defined $test->{info}{debug} && $test->{info}{debug} == 0 && $test->{info}->debug() == 0, - 'debug initialized off' + 'Debug initialized off' ); $test->{info}->debug(1); - ok($test->{info}{debug} && $test->{info}->debug(), 'debug on'); + ok($test->{info}{debug} && $test->{info}->debug(), 'Debug on'); $test->{info}->debug(0); - ok($test->{info}{debug} == 0 && $test->{info}->debug() == 0, 'debug off'); + ok($test->{info}{debug} == 0 && $test->{info}->debug() == 0, 'Debug off'); } sub offline : Tests(4) { @@ -184,12 +163,12 @@ sub offline : Tests(4) { can_ok($test->{info}, 'offline'); - ok(!defined $test->{info}{Offline}, 'offline not initialized'); + ok(!defined $test->{info}{Offline}, 'Offline not initialized'); $test->{info}->offline(1); - ok($test->{info}{Offline} && $test->{info}->offline(), 'offline mode on'); + ok($test->{info}{Offline} && $test->{info}->offline(), 'Offline mode on'); $test->{info}->offline(0); ok($test->{info}{Offline} == 0 && $test->{info}->offline() == 0, - 'offline off'); + 'Offline off'); } sub bulkwalk : Tests(4) { @@ -202,13 +181,13 @@ sub bulkwalk : Tests(4) { ok( !defined $test->{info}{BulkWalk} || ($test->{info}{BulkWalk} == 0 && $test->{info}->bulkwalk() == 0), - 'bulkwalk initialized off' + 'Bulkwalk initialized off' ); $test->{info}->bulkwalk(1); - ok($test->{info}{BulkWalk} && $test->{info}->bulkwalk(), 'bulkwalk on'); + ok($test->{info}{BulkWalk} && $test->{info}->bulkwalk(), 'Bulkwalk on'); $test->{info}->bulkwalk(0); ok($test->{info}{BulkWalk} == 0 && $test->{info}->bulkwalk() == 0, - 'bulkwalk off'); + 'Bulkwalk off'); } sub loopdetect : Tests(4) { @@ -216,12 +195,12 @@ sub loopdetect : Tests(4) { can_ok $test->{info}, 'loopdetect'; - ok(!defined $test->{info}{LoopDetect}, 'loopdetect not initialized'); + ok(!defined $test->{info}{LoopDetect}, 'Loopdetect not initialized'); $test->{info}->loopdetect(1); - ok($test->{info}{LoopDetect} && $test->{info}->loopdetect(), 'loopdetect on'); + ok($test->{info}{LoopDetect} && $test->{info}->loopdetect(), 'Loopdetect on'); $test->{info}->loopdetect(0); ok($test->{info}{LoopDetect} == 0 && $test->{info}->loopdetect() == 0, - 'loopdetect off'); + 'Loopdetect off'); } sub device_type : Tests(8) { @@ -381,7 +360,7 @@ sub snmp_ver : Tests(2) { sub specify : Tests(4) { my $test = shift; - can_ok $test->{info}, 'specify'; + can_ok($test->{info}, 'specify'); $test->{info}->cache_clear(); # Specify uses device_type(), use same data as that test to setup @@ -587,50 +566,80 @@ sub munge_ip : Tests(2) { "123.4.5.6", 'Binary IP to dotted ASCII munge'); } -sub munge_mac : Tests(3) { +sub munge_mac : Tests(4) { my $test = shift; can_ok($test->{info}, 'munge_mac'); - # The munge expects an octet string, pack a decimal string into - # representation munge is expecting - my $test_mac = pack("C*", split /\./, "01.35.69.103.137.171"); + # This is how these MACs look in a snmpwalk + my $test_mac = "01 23 45 67 89 AB"; + my $long_test_mac = "01 23 45 67 89 AB CD"; + my $short_test_mac = "01 23 45 67 89"; + + # However, they come across the wire as octet string, so we need to pack + # them. Before packing, we need to remove the whitespace + foreach ($test_mac, $long_test_mac, $short_test_mac) { + $_ =~ s/\s//g; + $_ = pack("H*", $_); + } + is(SNMP::Info::munge_mac($test_mac), "01:23:45:67:89:ab", 'Octet string to colon separated ASCII hex string'); - my $bogus_mac = pack("C*", split /\./, "01.35.69.103.137.171.02"); - is(SNMP::Info::munge_mac($bogus_mac), undef, - 'Bad octet string returns undef'); + is(SNMP::Info::munge_mac($long_test_mac), + undef, 'Too long of an octet string returns undef'); + is(SNMP::Info::munge_mac($short_test_mac), + undef, 'Too short of an octet string returns undef'); } -sub munge_prio_mac : Tests(3) { +sub munge_prio_mac : Tests(4) { my $test = shift; can_ok($test->{info}, 'munge_prio_mac'); - # The munge expects an octet string, pack a decimal string into - # representation munge is expecting - my $test_mac = pack("C*", split /\./, "01.35.69.103.137.171.205.239"); + # This is how these look in a snmpwalk + my $test_mac = "01 23 45 67 89 AB CD EF"; + my $long_test_mac = "01 23 45 67 89 AB CD EF 02"; + my $short_test_mac = "01 23 45 67 89"; + + # However, they come across the wire as octet string, so we need to pack + # them. Before packing, we need to remove the whitespace + foreach ($test_mac, $long_test_mac, $short_test_mac) { + $_ =~ s/\s//g; + $_ = pack("H*", $_); + } + is(SNMP::Info::munge_prio_mac($test_mac), "01:23:45:67:89:ab:cd:ef", 'Octet string to colon separated ASCII hex string'); - my $bogus_mac = pack("C*", split /\./, "01.35.69.103.137.171.205.239.02"); - is(SNMP::Info::munge_prio_mac($bogus_mac), - undef, 'Bad octet string returns undef'); + is(SNMP::Info::munge_prio_mac($long_test_mac), + undef, 'Too long of an octet string returns undef'); + is(SNMP::Info::munge_mac($short_test_mac), + undef, 'Too short of an octet string returns undef'); } -sub munge_prio_port : Tests(3) { +sub munge_prio_port : Tests(4) { my $test = shift; can_ok($test->{info}, 'munge_prio_port'); - # The munge expects an octet string, pack a decimal string into - # representation munge is expecting - my $test_mac = pack("C*", split /\./, "171.205"); + # This is how these look in a snmpwalk + my $test_mac = "AB CD"; + my $long_test_mac = "AB CD EF"; + my $short_test_mac = "AB"; + + # However, they come across the wire as octet string, so we need to pack + # them. Before packing, we need to remove the whitespace + foreach ($test_mac, $long_test_mac, $short_test_mac) { + $_ =~ s/\s//g; + $_ = pack("H*", $_); + } + is(SNMP::Info::munge_prio_port($test_mac), "ab:cd", 'Octet string to colon separated ASCII hex string'); - my $bogus_mac = pack("C*", split /\./, "171.205.02"); - is(SNMP::Info::munge_prio_port($bogus_mac), - undef, 'Bad octet string returns undef'); + is(SNMP::Info::munge_prio_port($long_test_mac), + undef, 'Too long of an octet string returns undef'); + is(SNMP::Info::munge_prio_port($short_test_mac), + undef, 'Too short of an string returns undef'); } # Can't see where this code is actually used, remove? @@ -639,9 +648,14 @@ sub munge_octet2hex : Tests(2) { can_ok($test->{info}, 'munge_octet2hex'); - # The munge expects an octet string, pack a decimal string into - # representation munge is expecting - my $test_mac = pack("C*", split /\./, "171.205"); + # This is how this looks in a snmpwalk + my $test_mac = "AB CD"; + + # However, is comes across the wire as octet string, so we need to pack + # it. Before packing, we need to remove the whitespace + $test_mac =~ s/\s//g; + $test_mac = pack("H*", $test_mac); + is(SNMP::Info::munge_octet2hex($test_mac), "abcd", 'Octet string to ASCII hex string'); } @@ -666,11 +680,36 @@ sub munge_bits : Tests(2) { '00010110', 'SNMP2 BITS field to ASCII bit string'); } -# TODO -#sub munge_counter64 : Tests() { -# my $test = shift; -# -#} +sub munge_counter64 : Tests(4) { + my $test = shift; + + my $hc_octets = 744002524365; + + can_ok($test->{info}, 'munge_counter64'); + is(SNMP::Info::munge_counter64(), undef, 'No arg returns undef'); + + # Default is no BigInt + is(SNMP::Info::munge_counter64(744002524365), + 744002524365, 'No BIGINT returns counter'); + +SKIP: { + eval { + require Math::BigInt; + 1; + } or do { + skip "Math::BigInt not installed", 1; + }; + + my $class = $test->class; + my $sess = $test->mock_session; + my $big_int_info + = $class->new('AutoSpecify' => 0, 'BigInt' => 1, 'Session' => $sess,); + + my $obj = SNMP::Info::munge_counter64(744002524365); + isa_ok($obj, 'Math::BigInt', 'Test counter64'); + + } +} sub munge_i_up : Tests(4) { my $test = shift; @@ -682,14 +721,43 @@ sub munge_i_up : Tests(4) { is(SNMP::Info::munge_i_up(7), 'lowerLayerDown', 'Lower layer down status'); } -sub munge_port_list : Tests(2) { +sub munge_port_list : Tests(6) { my $test = shift; can_ok($test->{info}, 'munge_port_list'); + # Start with the bit string since in a portlist each port is represented as + # a bit. # These are typically longer bit strings to cover the all ports in a switch my $bit_string = '01010101010101010101010101010101'; - my $bits = pack("B*", $bit_string); + my $bits_packed = pack("B*", $bit_string); + + # This is more for documentation than test code. When performing a snmpwalk + # the output will typically be a hex string. This converts the packed bit + # string above into a hex string as would be seen in the snmpwalk output. + my $unpacked_hex_string + = join(' ', map { sprintf "%02X", $_ } unpack('C*', $bits_packed)); + + # This should be the same as $unpacked_hex_string + my $hex_string = '55 55 55 55'; + + # Remove the spaces so we can pack, but preserve $hex_string for comparison + # testing with $unpacked_hex_string + (my $new_hex_string = $hex_string) =~ s/\s//g; + + # Pack the hex string for comparison with $bits_packed + my $new_hex_string_packed = pack("H*", $new_hex_string); + + # Finally unpack again to compare with original $bit_string + my $new_bit_string = unpack("B*", $new_hex_string_packed); + + # String comparison testing + is($unpacked_hex_string, $hex_string, + 'Unpacking binary bits into hex string is same as expected hex string'); + is($new_hex_string_packed, $bits_packed, + 'Packed hex string is equivalent to packed bit string'); + is($new_bit_string, $bit_string, + 'Unpacking packed hex string as binary results in original bit string'); # We are going to get a reference of array of bits back so convert the # string to an array @@ -697,8 +765,10 @@ sub munge_port_list : Tests(2) { for my $value (split //, $bit_string) { $expected->[++$#$expected] = $value; } - is_deeply(SNMP::Info::munge_port_list($bits), - $expected, 'Portlist octet string coverted to ASCII bit array'); + is_deeply(SNMP::Info::munge_port_list($bits_packed), + $expected, 'Portlist packed bit string coverted to ASCII bit array'); + is_deeply(SNMP::Info::munge_port_list($new_hex_string_packed), + $expected, 'Portlist packed hex string coverted to ASCII bit array'); } sub munge_null : Tests(2) { @@ -733,7 +803,7 @@ sub munge_e_type : Tests(3) { '.100.3.6.1.2.1.11.4', 'OID returned when unable to translate'); } -sub init : Tests(4) { +sub init : Tests(3) { my $test = shift; can_ok($test->{info}, 'init'); @@ -763,5 +833,937 @@ sub init : Tests(4) { $SNMP::VERSION = $netsnmp_ver; } +sub args : Tests(2) { + my $test = shift; + + # Match args passed to new() in My::Test::Class + my $sess = $test->mock_session; + my $args = { + 'AutoSpecify' => 0, + 'BulkWalk' => 0, + 'UseEnums' => 1, + 'RetryNoSuch' => 1, + 'DestHost' => '127.0.0.1', + 'Community' => 'public', + 'Version' => 2, + 'Session' => $sess, + }; + + can_ok($test->{info}, 'args'); + is_deeply($test->{info}->args(), + $args, 'Args returned match those passed to new()'); +} + +# Rename this test to prevent conflicts/recursion within test class +sub class_call : Tests(2) { + my $test = shift; + my $class = $test->class; + + can_ok($test->{info}, 'class'); + is($test->{info}->class(), $class, 'Class method returns object class'); +} + +sub error_throw : Tests(7) { + my $test = shift; + + my $error_str = "Test Error String\n"; + + can_ok($test->{info}, 'error_throw'); + + is($test->{info}->error_throw(), undef, 'No error provided returns undef'); + is($test->{info}->error(), undef, '... and no error()'); + is($test->{info}->error_throw($error_str), + undef, 'Error provided returns undef'); + + # Since we don't call with no_clear flag the error is cleared + is( + $test->{info}->error(), + "Test Error String\n", + '... and error() returns error string of call' + ); + + # Turn on debug to check carp of error + $test->{info}->debug(1); + warning_is { $test->{info}->error_throw($error_str) } + [{carped => 'Test Error String'}], 'Error carped when debug turned on'; + $test->{info}->debug(0); + is( + $test->{info}->error(), + "Test Error String\n", + '... and error() returns error string of call' + ); +} + +sub funcs : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'funcs'); + + subtest 'Funcs can() subtest' => sub { + + my $test_funcs = $test->{info}->funcs; + foreach my $key (keys %$test_funcs) { + can_ok($test->{info}, $key); + } + }; +} + +sub globals : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'globals'); + + subtest 'Globals can() subtest' => sub { + + my $test_globals = $test->{info}->globals; + foreach my $key (keys %$test_globals) { + can_ok($test->{info}, $key); + } + }; +} + +sub mibs : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'mibs'); + + subtest 'MIBs loaded subtest' => sub { + + my $mibs = $test->{info}->mibs(); + + foreach my $key (keys %$mibs) { + my $qual_name = "$key" . '::' . "$mibs->{$key}"; + ok(defined $SNMP::MIB{$mibs->{$key}}, "$qual_name defined"); + like(SNMP::translateObj($qual_name), + qr/^(\.\d+)+$/, "$qual_name translates to a OID"); + } + }; +} + +sub munge : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'munge'); + + subtest 'Munges subtest' => sub { + + my $test_munges = $test->{info}->munge(); + foreach my $key (keys %$test_munges) { + isa_ok($test_munges->{$key}, 'CODE', "$key munge"); + } + }; +} + +sub nosuch : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'nosuch'); + is($test->{info}->nosuch(), 1, 'RetryNoSuch on by default'); +} + +sub session : Tests(4) { + my $test = shift; + my $sess = $test->mock_session; + + can_ok($test->{info}, 'session'); + is_deeply($test->{info}->session(), $sess, 'Session returned'); + + # This will not be a mocked_session so object type and session will be + # different + my $new_sess = SNMP::Session->new( + DestHost => '127.0.0.1', + Community => 'new_public', + Version => 2, + ); + is_deeply($test->{info}->session($new_sess), + $new_sess, 'New session returned'); + isa_ok($test->{info}->session(), 'SNMP::Session', 'New session object'); +} + + +sub store : Tests(4) { + my $test = shift; + + # The store method itself doesn't enforce the naming, so we'll test + # with some totally made up data for 2nd attribute + my $store_data = { + 'i_description' => + {10 => 'Test-Description-10', 20 => 'Test-Description-20'}, + 'test_attribute' => {Key1 => 'Value 1', Key2 => 'Value 2'} + }; + + can_ok($test->{info}, 'store'); + + is_deeply($test->{info}->store(), {}, 'Store starts empty'); + ok($test->{info}->store($store_data), 'Insert test data into store'); + cmp_deeply( + $store_data, + $test->{info}->store(), + 'Store method returns test data' + ); +} + +sub private_global : Tests(15) { + my $test = shift; + + can_ok($test->{info}, '_global'); + + # This private method and dynamic creation of global methods is covered in + # can() tests. Use these tests to exercise code path for the load_, orig_, + # _raw, as well as, 'NOSUCHOBJECT' and 'NOSUCHINSTANCE' returns so the method + # calls will be indirect. + + # Some of these are defined in both SNMPv2-MIB and RFC1213-MIB so use + # OIDs to to make sure no issues with which one was loaded during tests + # Data for load_ + my $data = { + + # SNMPv2-MIB::sysContact OID = .1.3.6.1.2.1.1.4 + '.1.3.6.1.2.1.1.4' => {0 => 'NOSUCHOBJECT'}, + + # SNMPv2-MIB::sysName OID = .1.3.6.1.2.1.1.5 + '.1.3.6.1.2.1.1.5' => {0 => 'NOSUCHINSTANCE'}, + + # We'll use this to check _raw + # SNMPv2-MIB::sysServices OID = .1.3.6.1.2.1.1.7 + '.1.3.6.1.2.1.1.7' => {0 => 64}, + + # This is a leaf that we don't reference in %GLOBALS + # SNMPv2-MIB::snmpOutTraps OID = .1.3.6.1.2.1.11.29 + '.1.3.6.1.2.1.11.29' => {0 => 245}, + }; + + # Lets load cache with data to for initial tests + my $cache_data = {'_layers' => 4, '_name' => 'CacheTestName',}; + + # Cache expected after running tests + my $expected_cache = { + '_layers' => 64, + '_snmpOutTraps' => 245, + '_contact' => undef, + '_name' => undef, + 'store' => {}, + }; + + # Load the data for use in the mock session + $test->{info}{sess}{Data} = $data; + + # Load the cache + $test->{info}->cache($cache_data); + + is($test->{info}->name(), 'CacheTestName', + 'Call to name() loads cached data'); + is($test->{info}->layers(), + '00000100', 'Call to layers() loads cached data and munges'); + is($test->{info}->layers_raw(), + 4, 'Call to layers_raw() loads cached data without munge'); + is($test->{info}->load_layers(), + '01000000', 'Call to load_layers loads new data and munges'); + is($test->{info}->layers_raw(), + 64, 'Call to layers_raw() loads new data without munge'); + is($test->{info}->snmpOutTraps(), + 245, 'Call to snmpOutTraps() resolves MIB leaf and returns data'); + + is($test->{info}->load_contact(), + undef, 'Call to load_contact() returns undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_global(load_contact) NOSUCHOBJECT', + '... and throws error indicating NOSUCHOBJECT' + ); + is($test->{info}->load_name(), undef, 'Call to load_name() returns undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_global(load_name) NOSUCHINSTANCE', + '... and throws error indicating NOSUCHINSTANCE' + ); + is_deeply($test->{info}->cache(), + $expected_cache, 'Cache contains expected data'); + +# Not sure if we need this anonymous sub, added by this commit +# https://github.com/netdisco/snmp-info/commit/07605051f5886ffd05f65cfbc1804c316ed904ba + is(SNMP::Info::_global('CARP_TRACE'), + undef, 'CARP_TRACE passed to _global() returns undef'); + + # Simulate session error, i.e. get fails + $test->{info}{sess}{ErrorStr} = 'Get Failed'; + + # We need to force load to make it to error + is($test->{info}->load_name(), undef, 'Upon session error returned undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_global(load_name) Get Failed', + '... and error was thrown' + ); + + # Clear error or will impact future tests + $test->{info}{sess}{ErrorStr} = undef; +} + +sub private_set : Tests(12) { + my $test = shift; + + can_ok($test->{info}, '_set'); + + # Load cache with data so we can check that _set clears + my $cache_data = {'_name' => 'CacheTestName',}; + + $test->{info}->cache($cache_data); + is($test->{info}{_name}, 'CacheTestName', 'Cache has a name'); + + # Simple set + is($test->{info}->_set('name', 'TestName', 0), + 1, 'Valid non-array ref name _set() returned 1'); + is($test->{info}{_name}, undef, '... and now cache is cleared'); + + # 4 element array + my $arg_array = ['name', 'TestName', 0]; + is($test->{info}->_set($arg_array), + 1, 'Valid array reference name _set() returned 1'); + + # Reference to an array of 4 element arrays, also see set_multi + my $arg_aoa = [['name', 'TestName', 0]]; + is($test->{info}->_set($arg_aoa), + 1, 'Valid array of arrays reference name _set() returned 1'); + + # Bogus args + my $bogus_args = {'name' => 'TestName'}; + is($test->{info}->_set($bogus_args), undef, 'Invalid args returned undef'); + like( + $test->{info}->error(), + qr/SNMP::Info::_set.+-\sFailed/x, + '... and error was thrown' + ); + + # Bogus attr + is($test->{info}->_set('no_name', 'TestName', 0), + undef, 'Invalid attr returned undef'); + like( + $test->{info}->error(), + qr/SNMP::Info::_set.+-\sFailed\sto\sfind/x, + '... and error was thrown' + ); + + # Simulate session error, i.e. set fails + $test->{info}{sess}{ErrorStr} = 'Set Failed'; + is($test->{info}->_set('name', 'TestName', 0), + undef, 'Upon session error returned undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_set Set Failed', + '... and error was thrown' + ); + + # Clear error or will impact future tests + $test->{info}{sess}{ErrorStr} = undef; +} + +sub private_make_setter : Tests(10) { + my $test = shift; + + # This private method is covered in other tests + can_ok($test->{info}, '_make_setter'); + + # This private method and dynamic creation of methods is covered in + # can() tests. Use these tests to exercise code path for non-multi SNMP sets + # so the method calls will be indirect. This will indirectly exercise the + # AUTOLOAD and can methods as well. + + # Load cache with data so we can check that _set clears + my $cache_data = {'_name' => 'CacheTestName',}; + + $test->{info}->cache($cache_data); + is($test->{info}{_name}, 'CacheTestName', 'Cache has a name'); + + # Set on %GLOBALS entry name + is($test->{info}->set_name('TestName'), + 1, 'SNMP set on global name with no iid returned 1'); + is($test->{info}{_name}, undef, '... and now cache is cleared'); + + # Same set on the MIB leaf + is($test->{info}->set_sysName('TestName'), + 1, 'SNMP set on MIB leaf sysName with no iid returned 1'); + + # Can provide IID to global if wanted + is($test->{info}->set_name('TestName', 0), + 1, 'SNMP set on global name with iid returned 1'); + + # Set on a %FUNCS table method + is($test->{info}->set_i_alias('TestPortName', 3), + 1, 'SNMP set on func i_description with iid returned 1'); + + # Same set on the table MIB leaf + is($test->{info}->set_ifAlias('TestPortName', 3), + 1, 'SNMP set on MIB leaf ifAlias with iid returned 1'); + + # Simulate session error, i.e. set fails + $test->{info}{sess}{ErrorStr} = 'Set Failed'; + is($test->{info}->set_i_alias('TestPortName', 3), + undef, 'Upon session error returned undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_set Set Failed', + '... and error was thrown' + ); + + # Clear error or will impact future tests + $test->{info}{sess}{ErrorStr} = undef; +} + +sub set_multi : Tests(3) { + my $test = shift; + + can_ok($test->{info}, 'set_multi'); + + # This is contrived and mock set always returns true, so this test case + # could be improved. The multi_set method is meant for use similar to a + # database transaction where all sets must be completed in one atomic operation + my $multi_set = [ + ['i_up_admin', 2, 3], + ['i_description', 'Port Down', 3], + ['ifDescr', 'Test Descr', 4], + ]; + + is($test->{info}->set_multi($multi_set), 1, 'Valid multi_set() returned 1'); + + $multi_set = [ + ['i_up_admin', 2, 3], + ['i_description', 'Port Down', 3], + ['bogus_set', 'What?', 4], + ]; + + is($test->{info}->set_multi($multi_set), + undef, 'Invalid multi_set() returned undef'); + +} + +sub load_all : Tests(6) { + my $test = shift; + + can_ok($test->{info}, 'load_all'); + + # This method uses load_ and will not utilize the cache so we need to define + # data for the mocked session's get/getnext methods + + # Use OIDs to prevent resolution conflicts of fully qualified names between + # RFC1213-MIB and IF-MIB dependant upon which was loaded first via random + # hash ordering. We only need a subset of data to verify the method is + # working, no need to have data for all funcs the method calls. Use a subset + # of the IF-MIB::ifTable and IF-MIB::ifXTable + my $data = { + '.1.3.6.1.2.1.2.2.1.1' => {1 => 1, 2 => 2, 3 => 3,}, + '.1.3.6.1.2.1.2.2.1.2' => { + 1 => 'Loopback', + 2 => '1/3/1, 10/100 Ethernet TX', + 3 => '1/3/2, 10/100 Ethernet TX' + }, + '.1.3.6.1.2.1.2.2.1.3' => {1 => 24, 2 => 6, 3 => 6}, + '.1.3.6.1.2.1.2.2.1.4' => {1 => 1500, 2 => 1514, 3 => 1514}, + '.1.3.6.1.2.1.2.2.1.5' => {1 => 0, 2 => 100000000, 3 => 1000000000,}, + '.1.3.6.1.2.1.31.1.1.1.1' => {1 => 'system', 2 => '1/3/1', 3 => '1/3/2'}, + '.1.3.6.1.2.1.31.1.1.1.15' => {1 => 0, 2 => 100, 3 => 1000,}, + }; + + # Data is stored unmunged, OID's will be resolved and cache entries stored + # under %FUNCS names + my $expected_data = { + 'i_index' => {1 => 1, 2 => 2, 3 => 3,}, + 'i_description' => { + 1 => 'Loopback', + 2 => '1/3/1, 10/100 Ethernet TX', + 3 => '1/3/2, 10/100 Ethernet TX' + }, + 'i_type' => {1 => 24, 2 => 6, 3 => 6}, + 'i_mtu' => {1 => 1500, 2 => 1514, 3 => 1514}, + 'i_speed' => {1 => 0, 2 => 100000000, 3 => 1000000000,}, + 'i_name' => {1 => 'system', 2 => '1/3/1', 3 => '1/3/2'}, + 'i_speed_high' => {1 => 0, 2 => 100, 3 => 1000,}, + + # In base class defined as ifIndex + 'interfaces' => {1 => 1, 2 => 2, 3 => 3,}, + }; + + # Start with some data in store to verify it is overwritten + my $store_data + = {'i_description' => + {10 => 'Test-Description-10', 20 => 'Test-Description-20'}, + }; + + is_deeply($test->{info}->store(), {}, 'Store starts empty'); + ok($test->{info}->store($store_data), 'Insert test data into store'); + is_deeply($test->{info}->store(), $store_data, '... store now has test data'); + + # Load the data for use in the mock session + $test->{info}{sess}{Data} = $data; + + is_deeply($test->{info}->load_all(), + $expected_data, 'Call to load_all() returns expected data'); + is_deeply($test->{info}->store(), + $expected_data, '... and store now has expected data from load_all()'); +} + +# Need to rename from all to prevent name conflict +sub my_all : Tests(9) { + my $test = shift; + + can_ok($test->{info}, 'all'); + + # Use OIDs to prevent resolution conflicts of fully qualified names between + # RFC1213-MIB and IF-MIB dependant upon which was loaded first via random + # hash ordering. We only need a bare minimum of data to verify the method is + # working since it relies on load_all() which has its own tests. + my $data = { + '.1.3.6.1.2.1.2.2.1.2' => { + 1 => 'Loopback', + 2 => '1/3/1, 10/100 Ethernet TX', + 3 => '1/3/2, 10/100 Ethernet TX' + }, + }; + + # Data is stored unmunged, OID's will be resolved and cache entries stored + # under %FUNCS names + my $expected_data = { + 'i_description' => { + 1 => 'Loopback', + 2 => '1/3/1, 10/100 Ethernet TX', + 3 => '1/3/2, 10/100 Ethernet TX' + }, + }; + + # Start with some data in store to verify it is overwritten on first call + # and whatever is in store() is returned on subsequent calls to all + my $store_data + = {'i_description' => + {10 => 'Test-Description-10', 20 => 'Test-Description-20'}, + }; + + is_deeply($test->{info}->store(), {}, 'Store starts empty'); + ok($test->{info}->store($store_data), 'Insert test data into store'); + is_deeply($test->{info}->store(), $store_data, '... store now has test data'); + + # Load the data for use in the mock session + $test->{info}{sess}{Data} = $data; + + is_deeply($test->{info}->all(), + $expected_data, 'Call to all() returns expected data'); + is_deeply($test->{info}->store(), + $expected_data, '... and store now has expected data from all()'); + + ok($test->{info}->store($store_data), 'Re-insert test data into store'); + is_deeply($test->{info}->store(), + $store_data, '... store again has test data'); + + is_deeply($test->{info}->all(), + $expected_data, + '... call to all() returns test data, no call to load_all()'); +} + +sub private_load_attr : Tests(16) { + my $test = shift; + + can_ok($test->{info}, '_load_attr'); + + # This private method and dynamic creation of table aka func methods is + # covered in can() tests. Use these tests to exercise code path for + # the load_, orig_, _raw, as well as, 'NOSUCHOBJECT', 'NOSUCHINSTANCE', + # and 'ENDOFMIBVIEW' returns so the method calls will be indirect. + + # Currently mocked session in test harness doesn't support bulkwalk, so + # that code path is not tested + + # Some of these are defined in both SNMPv2-MIB and RFC1213-MIB so use + # OIDs to to make sure no issues with which one was loaded during tests + # Data for load_ + my $data = { + '.1.3.6.1.2.1.2.2.1.2' => { + 1 => 'Loopback', + 2 => '1/3/1, 10/100 Ethernet TX', + 3 => '1/3/2, 10/100 Ethernet TX' + }, + '.1.3.6.1.2.1.2.2.1.8' => {1 => 4, 2 => 'up', 3 => 7}, + 'IF-MIB::ifPromiscuousMode' => {1 => 'false', 2 => 'true', 3 => 'false'}, + 'IF-MIB::ifConnectorPresent' => {0 => 'NOSUCHOBJECT'}, + 'IF-MIB::ifCounterDiscontinuityTime' => {0 => 'NOSUCHINSTANCE'}, + 'IF-MIB::ifHCOutOctets' => + {1 => 0, 2 => 1828306359704, 3 => 1002545943585, 4 => 'ENDOFMIBVIEW'}, + }; + + # Load cache with data to for initial tests + my $cache_data = { + '_i_description' => 1, + '_i_up' => 1, + 'store' => { + 'i_description' => + {10 => 'Test-Description-10', 20 => 'Test-Description-20'}, + 'i_up' => {10 => 6, 20 => 7} + } + }; + + # Cache expected after running tests + # Note: i_up starts in cache and call to load_i_up for new data increments + # the cache counter + # Note: We don't call load_i_description so the data from cache never gets + # replaced + # Note: munge_i_up only munges integers 4-7 as 1-3 are already enumerated + my $expected_cache = { + '_i_description' => 1, + '_i_up' => 2, + '_ifPromiscuousMode' => 1, + '_ifConnectorPresent' => undef, + '_ifCounterDiscontinuityTime' => undef, + '_i_octet_out64' => 1, + 'store' => { + 'i_description' => + {10 => 'Test-Description-10', 20 => 'Test-Description-20'}, + 'i_up' => {1 => 4, 2 => 'up', 3 => 7}, + 'ifPromiscuousMode' => {1 => 'false', 2 => 'true', 3 => 'false'}, + 'i_octet_out64' => {1 => 0, 2 => 1828306359704, 3 => 1002545943585} + } + }; + + my $expected_cache_munge_iup = {10 => 'notPresent', 20 => 'lowerLayerDown'}; + my $expected_load_munge_iup + = {1 => 'unknown', 2 => 'up', 3 => 'lowerLayerDown'}; + + my $expected_load_raw_iftype = {1 => 24, 2 => 6, 3 => 6}; + + # Load the data for use in the mock session + $test->{info}{sess}{Data} = $data; + + # Load the cache + $test->{info}->cache($cache_data); + + is_deeply( + $test->{info}->i_description(), + $cache_data->{'store'}{'i_description'}, + 'Call to i_description() loads cached data' + ); + is_deeply($test->{info}->i_up(), + $expected_cache_munge_iup, 'Call to i_up() loads cached data and munges'); + is_deeply( + $test->{info}->i_up_raw(), + $cache_data->{'store'}{'i_up'}, + 'Call to i_up_raw() loads cached data without munge' + ); + is_deeply($test->{info}->load_i_up(), + $expected_load_munge_iup, 'Call to load_i_up() loads new data and munges'); + is_deeply( + $test->{info}->i_up_raw(), + $expected_cache->{'store'}{'i_up'}, + 'Call to i_up_raw() loads new data without munge' + ); + + # Test ability to use MIB leaf + is_deeply( + $test->{info}->ifPromiscuousMode(), + $data->{'IF-MIB::ifPromiscuousMode'}, + 'Call to ifPromiscuousMode() resolves MIB leaf and returns data' + ); + + # Test error conditions + is($test->{info}->load_ifConnectorPresent(), + undef, 'Call to load_ifConnectorPresent() returns undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_load_attr: load_ifConnectorPresent : NOSUCHOBJECT', + '... and throws error indicating NOSUCHOBJECT' + ); + is($test->{info}->load_ifCounterDiscontinuityTime(), + undef, 'Call to load_ifCounterDiscontinuityTime() returns undef'); + is( + $test->{info}->error(), + 'SNMP::Info::_load_attr: load_ifCounterDiscontinuityTime : NOSUCHINSTANCE', + '... and throws error indicating NOSUCHINSTANCE' + ); + + # 'ENDOFMIBVIEW' isn't an error condition, it just stops the walk + # Ask for raw since don't want munge_counter64 to turn results into objects + # and want to compare to what will be stored in cache at the end + is_deeply( + $test->{info}->i_octet_out64_raw(), + $expected_cache->{'store'}{'i_octet_out64'}, + 'Call to i_up_raw() loads new data without munge' + ); + + # Test partial fetches + is_deeply( + $test->{info}->i_octet_out64_raw(3), + +{3 => 1002545943585}, + 'Partial call to i_octet_out64_raw(3) data without munge' + ); + is_deeply( + $test->{info}->i_description(2), + +{2 => '1/3/1, 10/100 Ethernet TX'}, + 'Partial call to i_description(2) loads new data' + ); + ok(!exists $test->{info}{store}{i_description}{2}, + '... and does not store it in cache'); + + is_deeply($test->{info}->cache(), + $expected_cache, 'Cache contains expected data'); +} + +sub private_show_attr : Tests(3) { + my $test = shift; + + can_ok($test->{info}, '_show_attr'); + + # Load cache with data + my $cache_data = {'_i_up' => 1, 'store' => {'i_up' => {10 => 6, 20 => 7}}}; + + # Load the cache + $test->{info}->cache($cache_data); + + my $expected_munge = {10 => 'notPresent', 20 => 'lowerLayerDown'}; + + # Minimal tests as this method is heavily covered in other testing + is_deeply($test->{info}->_show_attr('i_up'), + $expected_munge, 'Shows munged data from cache without raw flag'); + is_deeply( + $test->{info}->_show_attr('i_up', 1), + $cache_data->{'store'}{'i_up'}, + 'Shows unmunged data from cache with raw flag' + ); +} + +sub snmp_connect_ip : Tests(4) { + my $test = shift; + + can_ok($test->{info}, 'snmp_connect_ip'); + + is($test->{info}->snmp_connect_ip('127.0.0.1', 2, 'public'), + undef, 'Connect to loopback returns undef'); + is($test->{info}->snmp_connect_ip('0.0.0.0', 2, 'public'), + undef, 'Connect to zeros returns undef'); + + is($test->{info}->snmp_connect_ip('demo.snmplabs.com', 2, 'public'), + 1, 'Connect to demo.snmplabs.com returns 1'); +} + +sub modify_port_list : Tests(4) { + my $test = shift; + + can_ok($test->{info}, 'modify_port_list'); + + # Let munge_port_list do the work of converting this into the portlist array + my $orig_plist = SNMP::Info::munge_port_list(pack("B*", '01010101')); + my $new_plist_1 = pack("B*", '01110101'); + my $new_plist_0 = pack("B*", '01110001'); + my $expanded_plist = pack("B*", '0111000100000001'); + + # This call will actually modify $orig_plist + is($test->{info}->modify_port_list($orig_plist, 2, 1), + $new_plist_1, 'Bit in offset position 2 changed to on'); + + # Here we start with modified $orig_plist, now '01110101' + is($test->{info}->modify_port_list($orig_plist, 5, 0), + $new_plist_0, 'Bit in offset position 5 changed to off'); + + # Modified $orig_plist, now '01110001' + is($test->{info}->modify_port_list($orig_plist, 15, 1), + $expanded_plist, + 'Bit in offset position 15 changed to on and portlist array expanded'); +} + +sub private_cache : Tests(1) { + my $test = shift; + + # This method is covered in private_global and private_load_attr + # tests so just cover with can() here + can_ok($test->{info}, '_cache'); +} + +sub private_munge : Tests(1) { + my $test = shift; + + # This method is covered in private_global and private_load_attr + # tests so just cover with can() here + can_ok($test->{info}, '_munge'); +} + +sub private_validate_autoload_method : Tests(8) { + my $test = shift; + + can_ok($test->{info}, '_validate_autoload_method'); + + subtest '%GLOBALS _validate_autoload_method() subtest' => sub { + + foreach my $prefix ('', 'load_', 'orig_') { + my $test_global = "$prefix" . 'contact'; + is_deeply( + $test->{info}->_validate_autoload_method("$test_global"), + ['.1.3.6.1.2.1.1.4.0', 0], + qq(Global '$test_global' validates) + ); + } + is_deeply( + $test->{info}->_validate_autoload_method('set_contact'), + ['.1.3.6.1.2.1.1.4', 0], + q(Global 'set_contact' validates) + ); + is_deeply( + $test->{info}->_validate_autoload_method('contact_raw'), + ['.1.3.6.1.2.1.1.4.0', 0], + q(Global 'contact_raw' validates) + ); + }; + + # Use a leaf we don't have defined in %GLOBALS that is read/write to pass + # all tests, we'll test access separately + subtest 'Single instance MIB leaf _validate_autoload_method() subtest' => + sub { + + foreach my $prefix ('', 'load_', 'orig_') { + my $test_global = "$prefix" . 'snmpEnableAuthenTraps'; + is_deeply( + $test->{info}->_validate_autoload_method("$test_global"), + ['.1.3.6.1.2.1.11.30.0', 0], + qq(MIB leaf '$test_global' validates) + ); + } + is_deeply( + $test->{info}->_validate_autoload_method('set_snmpEnableAuthenTraps'), + ['.1.3.6.1.2.1.11.30', 0], + q(MIB leaf 'set_snmpEnableAuthenTraps' validates) + ); + is_deeply( + $test->{info}->_validate_autoload_method('snmpEnableAuthenTraps_raw'), + ['.1.3.6.1.2.1.11.30.0', 0], + q(MIB leaf 'snmpEnableAuthenTraps_raw' validates) + ); + }; + + subtest '%FUNCS _validate_autoload_method() subtest' => sub { + + foreach my $prefix ('', 'load_', 'orig_') { + my $test_global = "$prefix" . 'i_alias'; + is_deeply( + $test->{info}->_validate_autoload_method("$test_global"), + ['.1.3.6.1.2.1.31.1.1.1.18', 1], + qq(Func '$test_global' validates) + ); + } + is_deeply( + $test->{info}->_validate_autoload_method('set_i_alias'), + ['.1.3.6.1.2.1.31.1.1.1.18', 1], + q(Func 'set_i_alias' validates) + ); + is_deeply( + $test->{info}->_validate_autoload_method('i_alias_raw'), + ['.1.3.6.1.2.1.31.1.1.1.18', 1], + q(Func 'i_alias_raw' validates) + ); + }; + + # Use a leaf we don't have defined in %FUNCS that is read/write to pass + # all tests, we'll test access separately + subtest 'Table MIB leaf _validate_autoload_method() subtest' => sub { + + foreach my $prefix ('', 'load_', 'orig_') { + my $test_global = "$prefix" . 'ifPromiscuousMode'; + is_deeply( + $test->{info}->_validate_autoload_method("$test_global"), + ['.1.3.6.1.2.1.31.1.1.1.16', 1], + qq(Func '$test_global' validates) + ); + } + is_deeply( + $test->{info}->_validate_autoload_method('set_ifPromiscuousMode'), + ['.1.3.6.1.2.1.31.1.1.1.16', 1], + q(Func 'set_ifPromiscuousMode' validates) + ); + is_deeply( + $test->{info}->_validate_autoload_method('ifPromiscuousMode_raw'), + ['.1.3.6.1.2.1.31.1.1.1.16', 1], + q(Func 'ifPromiscuousMode_raw' validates) + ); + }; + + # Test that not-accessible leaf returns undef IF-MIB::ifStackHigherLayer + is($test->{info}->_validate_autoload_method('ifStackHigherLayer'), + undef, q(MIB leaf 'ifStackHigherLayer' not-accessible, returns undef)); + + # Test that read-only leaf won't validate set_ + is($test->{info}->_validate_autoload_method('set_i_lastchange'), + undef, + q(Func 'i_lastchange' is read-only, 'set_i_lastchange' returns undef)); + + # Check fully qualified MIB leaf w substitutions validates + is_deeply( + $test->{info}->_validate_autoload_method('IF_MIB__ifConnectorPresent'), + ['.1.3.6.1.2.1.31.1.1.1.17', 1], + q(Fully qualified 'IF_MIB__ifConnectorPresent' validates) + ); +} + +# Prefix with private as we don't want to accidentally override in test class +sub private_can : Tests(9) { + my $test = shift; + + # This method is heavily covered across tests, just verify here that + # a successful call places the method in the symbol table + + # See perldoc Symbol, specifically Symbol::delete_package. We can't assume + # symbols are deleted with the object between tests. Since we can() all + # globals and funcs during tests use MIB leafs to test table and scalar + # methods. The symbol_test() method is defined in My::Test::Class + # Note: if these tests start to fail make sure we aren't using the leaf + # in other tests + + # This leaf tests the global path SNMPv2-MIB::snmpInBadCommunityNames + is($test->symbol_test('snmpInBadCommunityNames'), + 0, q(Method 'snmpInBadCommunityNames' is not defined in the symbol table)); + can_ok($test->{info}, 'snmpInBadCommunityNames'); + is($test->symbol_test('snmpInBadCommunityNames'), + 1, q(Method 'snmpInBadCommunityNames' is now defined in the symbol table)); + + # This leaf tests the table path IF-MIB::ifCounterDiscontinuityTime + is($test->symbol_test('ifCounterDiscontinuityTime'), + 0, + q(Method 'ifCounterDiscontinuityTime' is not defined in the symbol table)); + can_ok($test->{info}, 'ifCounterDiscontinuityTime'); + is($test->symbol_test('ifCounterDiscontinuityTime'), + 1, + q(Method 'ifCounterDiscontinuityTime' is now defined in the symbol table)); + + # This leaf tests the set_ path SNMPv2-MIB::snmpSetSerialNo + is($test->symbol_test('set_snmpSetSerialNo'), + 0, q(Method 'set_snmpSetSerialNo' is not defined in the symbol table)); + can_ok($test->{info}, 'set_snmpSetSerialNo'); + is($test->symbol_test('set_snmpSetSerialNo'), + 1, q(Method 'set_snmpSetSerialNo' is now defined in the symbol table)); +} + +# Prefix with private as we don't want to accidentally override in test class +sub private_autoload : Tests(3) { + my $test = shift; + + # This method is covered in other tests, just verify here that + # a successful call places the method in the symbol table. Since can() does + # the majority of the work call a method without calling can() first. Same + # as noted in the private_can test we need to use a leaf not used elsewhere + # (IP-MIB::ipDefaultTTL) to make sure method isn't in the symbol table first. + # AUTOLOAD calls the method after inserted in the symbol table, so we + # populate cache to return some data + + # Load the cache + my $cache_data = {'_ipDefaultTTL' => 64}; + $test->{info}->cache($cache_data); + + is($test->symbol_test('ipDefaultTTL'), + 0, q(Method 'ipDefaultTTL' is not defined in the symbol table)); + is($test->{info}->ipDefaultTTL(), + 64, q(Method 'ipDefaultTTL' called and returned expected data')); + is($test->symbol_test('ipDefaultTTL'), + 1, q(Method 'ipDefaultTTL' is now defined in the symbol table)); +} + +# Prefix with private as we don't want to accidentally override in test class +sub private_destroy : Tests(2) { + my $test = shift; + + can_ok($test->{info}, 'DESTROY'); + is($test->{info}->DESTROY(), undef, 'DESTROY returns undef'); +} 1;