Refactor perl sub for testability - perl

I have inherited a perl code base. Consider the following subroutine;
sub getSysRTable
{
my $iface = shift;
return if not length($iface);
my %ip_routes;
my #routes = `/usr/bin/netstat -rn`;
foreach my $route(#routes) {
if ($route =~ /([\S.]+)\s+([\d.]+.[\d.]+.[\d.]+.[\d.]+)\s+(UGS|UGHS)\s+($iface)/ )
{ $ip_routes {$1} = $2 }
}
return %ip_routes;
}
I want to write unit tests for this code. The testing I have in mind will use sample output from netstat -rn and check for expected behaviour. The sub as is, invokes a command, so injecting my test data is problematic with this implementation.
What is the idiomatic perlish approach to refactoring this sub for testability?

First, change your code as follows:
sub getDataForSysRTable {
return `/usr/bin/netstat -rn`;
}
sub getSysRTable
{
my $iface = shift;
return if not length($iface);
my %ip_routes;
my #routes = getDataForSysRTable();
foreach my $route(#routes) {
if ($route =~ /([\S.]+)\s+([\d.]+.[\d.]+.[\d.]+.[\d.]+)\s+(UGS|UGHS)\s+($iface)/ )
{ $ip_routes {$1} = $2 }
}
return %ip_routes;
}
Then for your test, you can do
local *getDataForSysRTable = sub {
... return known data ...
};
my $ip_routes = getSysRTable($iface);

Related

Possible to call STORE on deeper level tied hash assignment?

I'm trying to write a Perl module for a “persistent YAML hash”, with the following properties:
With every access, check if the YAML file has changed, and if so, reload.
As soon as any data in the hash is changed, save.
Don't save on UNTIE, so that the file isn't updated when you only read values.
My first attempt seemed to work pretty well:
package YAMLHash;
use v5.24;
use warnings;
use experimental 'signatures';
use YAML::XS qw(DumpFile LoadFile);
use File::stat;
sub refresh($self)
{
if (-f $self->{file}) {
if (stat($self->{file})->mtime > $self->{mtime}) {
$self->{data} = LoadFile($self->{file});
$self->{mtime} = stat($self->{file})->mtime;
}
}
}
sub save($self)
{
DumpFile($self->{file}, $self->{data});
$self->{mtime} = stat($self->{file})->mtime;
}
sub TIEHASH($class, #args)
{
my ($filename) = $args[0];
die "No filename specified" unless $filename;
my $self = bless { data=>{}, file=>$filename, mtime=>0 }, $class;
refresh($self);
return $self;
}
sub FETCH($self, $key = '')
{
refresh($self);
return $self->{data}{$key};
}
sub EXISTS($self, $key)
{
refresh($self);
return exists($self->{data}{$key});
}
sub FIRSTKEY($self)
{
refresh($self);
my #ignore = keys %{$self->{data}}; # reset iterator
return each %{$self->{data}};
}
sub NEXTKEY($self, $lastkey)
{
refresh($self);
return each %{$self->{data}};
}
sub SCALAR($self)
{
return scalar %{$self->{data}};
}
sub STORE($self, $key, $value)
{
refresh($self);
$self->{data}{$key} = $value;
save($self);
}
sub DELETE($self, $key)
{
refresh($self);
delete $self->{data}{$key};
save($self);
}
sub CLEAR($self, $key)
{
$self->{data} = {};
save($self);
}
1;
I tried this as follows:
use YAMLHash;
tie my %foo, 'YAMLHash', 'test.yaml';
$foo{hello} = 'world';
$foo{answer} = 42;
$foo{counter}++;
and the resulting YAML file looks like this:
---
answer: 42
counter: 1
hello: world
But then I changed my example code to:
use YAMLHash;
tie my %foo, 'YAMLHash', 'test.yaml';
$foo{hello} = 'world';
$foo{answer} = 42;
$foo{counter}++;
$foo{a}{b}{c}{d} = 'e';
and the result is:
---
a: {}
answer: 42
counter: 2
hello: world
So, obviously, STORE is called when $foo{a} is created, but not when $foo{a}{b}{c}{d} is assigned.
Is there any way to make this do what I want?
You will need to tie %{ $foo{a} }, %{ $foo{a}{b} } and %{ $foo{a}{b}{c} } as well.
You could recursively tie the hashes and arrays in the data structure in TIEHASH. Don't forget to the do the same thing to data added to the structure via STORE!
You might want to use a different class for the root of the data structure and non-root nodes.
Warning: Using tie will make accesses slower.
Note that you need to tie the scalars too, not just the hashes (and arrays). All of the following change the value of a hash element without calling STORE:
Changing the scalar directly:
++$foo{a};
chomp($foo{a});
$foo{a} =~ s/x/y/g;
...
Changing a scalar via an alias or a reference:
my \$x = \$foo{a}; $x = 123;
my $r = \$foo{a}; $$r = 123;
for ($foo{a}) { $_ = 123; }
sub { $_[0] = 123; }->($foo{a});
...

perl: Can't call method "push" without a package or object reference?

I was given the assignment to implement a linked list in perl without using the built-in push,pop,shift and unshift. This is my first time learning perl, coming from c++ and java, this is what I came up with:
#!/usr/bin/perl
sub node {
my (#value) = #_;
sub get {
$next;
}
sub push {
#my $next = \#_;
if(defined($next))
{
$next->push(#_);
}
else
{
my $next = \#_;
}
}
sub size {
if(defined($next))
{
$next->size($_[0]);
}
else
{
$_[0]+1;
}
}
sub myprint {
print "$_[0]: ";
foreach (#value) {
print "$_, ";
}
print "\n";
if(defined($next)) {
$next->print($_[0]+1);
}
}
}
while(!defined($done))
{
print "what do you want to do?\n";
print "1 -- push\n";
print "2 -- print nodes\n";
print "3 -- pop\n";
print "4 -- quit\n";
my $val = <STDIN>;
if ($val == 1)
{
print "Type something: ";
$input = <STDIN>;
if(defined($top))
{
$top->push(node($input));
}
else
{
$top = node($input);
}
}
elsif ($val == 2)
{
if(defined($top))
{
$top->myprint(1);
}
}
elsif ($val == 3)
{
if(defined($top))
{
if(defined($top->next))
{
$top=$top->next;
}
}
}
elsif ($val == 4)
{
$done=true;
}
else
{
print "Invalid option\n";
}
}
output:
what do you want to do?
1 -- push
2 -- print nodes
3 -- pop
4 -- quit
1
Type something: q
what do you want to do?
1 -- push
2 -- print nodes
3 -- pop
4 -- quit
1
Type something: w
Can't call method "push" without a package or object reference at ./linkedlistattempt1.pl line 76, <STDIN> line 4.
I'm guessing the "->" operator can only be used with a module or package.
I haven't gotten around to testing the other methods, I'm still working on push. I feel like the best way of doing this is to simply have a holder sub like `sub holder { $value = \#_; } but I don't understand how I would (could?) add more variables, like the next node, hence the sub within a sub design. So without the help of perl's built in functions, how would I do this?
Its important to mention I'm interested in methods that can run on the older versions, going down to 5.10. Most if not all of the tutorials are showing stuff for 5.16 or 5.18
A typical Perl implementation using classic Perl OO would look something like this. Read the man pages perlootut and perlobj to learn how it works.
#!/usr/bin/perl
use strict;
use warnings;
package LinkedList::Node;
# Constructor.
sub new {
my ($class, $item) = #_;
my $self = { item => $item };
return bless($self, $class);
}
# Read-only accessor.
sub item {
my $self = shift;
return $self->{item};
}
# Read-write accessor.
sub next {
my $self = shift;
my $next = $self->{next};
if (#_ > 0) {
$self->{next} = shift;
}
return $next;
}
package LinkedList;
# Constructor. Creates an empty linked list.
sub new {
my $class = shift;
return bless({}, $class);
}
# Read-only accessor.
sub head {
my $self = shift;
return $self->{head};
}
# Insert an item at the beginning.
sub push {
my ($self, $item) = #_;
my $node = LinkedList::Node->new($item);
my $head = $self->{head};
if ($head) {
$node->next($head);
}
$self->{head} = $node;
}
package main;
my $list = LinkedList->new;
$list->push(2);
$list->push(5);
$list->push(9);
for (my $node = $list->head; $node; $node = $node->next) {
print($node->item, "\n");
}

Making iteration recursive using PERL

Bon,
I have this code which is a simple loop, which works… to call it you need to send it a reference to an array of numbers.
#blah = (0b0010,0b010,0b0111);
$ans = &logical_loop(\#blah);
sub logical_loop()
{
my $codes = $_[0];
my $ans = 0;
foreach (#$codes) {
printf "%b\n",$_;
$ans = ($ans | $_)
}
return($ans);
}
I wanted to make it recursive, so I wrote this code which doesn't work…. please can somebody tell me what I have missed? something to do with variables scopes perhaps?
sub recursive_loop
{
my $codes = $_[0];
my $el = shift #$codes;
if (#$codes == ()) {
return ($el | $answer);
} else {
$answer = (&recursive_loop(\#$codes) | $el);
}
}
sub recursive_loop {
return 0 unless #_;
my $head = shift;
return $head | recursive_loop(#_);
}
#blah = (0b0010,0b010,0b0111);
recursive_loop(#blah);
More efficient tail recursive:
sub or_it_up {
unshift #_, 0;
goto &recursive_loop;
}
sub recursive_loop {
my $ans = shift;
return $ans unless #_;
unshift #_, $ans | shift;
goto &recursive_loop;
}
#blah = (0b0010,0b010,0b0111);
or_it_up(#blah);
You can use calling recursive_loop as function but in this way it will not make stack frame.
You can also write it simply without or_it_up which serves just educational purpose. Calling recursive_loop directly will make result caused by nature of binary or.
I see a few problems with your subroutine.
its name contains a spacing mark
it doesn't call itself therefore no recursion is possible

Perl Google Yahoo And etc Engine didn't bring results, Need help perl expert

I make the scanner using perl, but when i try to test the script i got blank results on the some engine, didn't give me results, may someone help me to check the script where line is wrong writing in perl?
Thanks
sub google() {
my #list;
my $key = $_[0];
for (my $i=0; $i<=1000; $i+=100){
my $search = ("http://www.google.com/search?q=".key($key)."&num=100&filter=0&start=".$i);
my $res = search_engine_query($search);
while ($res =~ m/<a href=\"?http:\/\/([^>\"]*)\//g) {
my $link = $1;
if ($link !~ /google/){
my #grep = links($link);
push(#list,#grep);
}
}
}
return #list;
}
sub search_engine() {
my (#total,#clean);
my $chan = $_[0];
my $bug = $_[1];
my $dork = $_[2];
my $engine = $_[3];
my $logo = $_[4];
if ($engine eq "GooGLe") { my #google = google($dork); push(#total,#google); }
if ($engine eq "AllTheWeb") { my #alltheweb = alltheweb($dork); push(#total,#alltheweb); }
if ($engine eq "Bing") { my #bing = bing($dork); push(#total,#bing); }
if ($engine eq "ALtaViSTa") { my #altavista = altavista($dork); push(#total,#altavista); }
if ($engine eq "AsK") { my #ask = ask($dork); push(#total,#ask); }
if ($engine eq "UoL") { my #uol = uol($dork); push(#total,#uol); }
if ($engine eq "YahOo") { my #yahoo = yahoo($dork); push(#total,#yahoo); }
#clean = clean(#total);
&msg("$chan","$logo(7#2$engine15)12 Total:4 (".scalar(#total).")12 Clean:4 (".scalar(#clean).")");
return #clean;
}
if ($engine =~ /google/i) {
if (my $pid = fork) { waitpid($pid, 0); }
else { if (fork) { exit; } else {
&lfc($chan,$bug,$dork,"GooGLe");
} exit; }
}
You can download n0body Scanner at here.
For one thing, you are using a zero-argument prototype for a function that you pass an argument to. Don't do that.
For another, are you inspecting the response whenever you are unhappy with the results? Is the response something other than 200? Does the response include an error message from the search engine?
Based on this line alone:
&msg("$chan",
"$logo(7#2$engine15)12 Total:4 (".scalar(#total).")12 Clean:4 (".scalar(#clean).")");
I also conclude that you're not using use strict and use warnings. Do that.

Can this perl wrapper function be extended to work with any input function?

Consider the following wrapper function that retrys a given function some given number of times if the function throws (not sure why the formatting is wonky):
sub tryit{
my $fun = shift;
my $times = shift;
my #args = #_;
my $ret;
do{
$times--;
eval{
$ret = $fun->(#args);
};
if($#){
print "Error attemping cmd: $#\n";
}
else{
return $ret;
}
}while($times > 0);
return;
}
How can this be extended so that the return value of the parameter function is properly propigated up no matter what kind of value is returned? For instance, this function won't pass an array up properly. You can't just return $fun->() because the return only takes you out of the eval block.
Same basic answer as Nemo, but with some improvements:
Safer exception handling.
The exception of the last try isn't caught.
Error sent to STDERR.
Extra newline removed.
Cleaner loop.
Better variable names.
wantarray will get you the information you need.
sub tryit {
my $func = shift;
my $attempts = shift;
my $list_wanted = wantarray;
my #rv;
for (2..$attempts) {
if (eval{
if ($list_wanted) {
#rv = $func->(#_);
} else {
$rv[0] = $func->(#_);
}
1 # No exception
}) {
return $list_wanted ? #rv : $rv[0];
}
warn($#, "Retrying...\n");
}
return $func->(#_);
}
Void context gets propagated as void context, but that's probably acceptable. If not, it's easy to adjust.
You can do this with wantarray. (It is formatting wonky for me, too; sorry)
sub tryit{
my $fun = shift;
my $times = shift;
my #args = #_;
my $array_wanted = wantarray;
my $ret;
my #ret;
do{
$times--;
eval{
if ($array_wanted) {
#ret = $fun->(#args);
}
else {
$ret = $fun->(#args);
}
};
if($#){
print "Error attemping cmd: $#\n";
}
else{
if ($array_wanted) {
return #ret;
}
else {
return $ret;
}
}
}while($times > 0);
return;
}
I am sure a monster Perl hacker could find a way to tighten this up, but that is the basic idea.