From f6f920eb3971a0d239b564fb53d7176e97ece9be Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 7 Jun 2020 22:55:52 -0400 Subject: [PATCH] Use the "modulino" pattern to facilitate unit tests Now the `ddclient` file can be used as a script or as a module. For details, see: https://www.drdobbs.com/scripts-as-modules/184416165 Addresses #147 --- ddclient.template | 189 +++++++++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 88 deletions(-) diff --git a/ddclient.template b/ddclient.template index 1d63508..eb3e7e9 100755 --- a/ddclient.template +++ b/ddclient.template @@ -18,6 +18,7 @@ # # ###################################################################### +package ddclient; require v5.10.1; use strict; use warnings; @@ -25,7 +26,8 @@ use Getopt::Long; use Sys::Hostname; use IO::Socket; -my $version = "3.9.1"; +use version 0.77; our $VERSION = version->declare('v3.9.1'); +(my $version = $VERSION->stringify()) =~ s/^v//; my $programd = $0; $programd =~ s%^.*/%%; my $program = $programd; @@ -64,6 +66,11 @@ local $lineno = ''; $ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:"; +my ($result, %config, %globals, %cache); +my $saved_cache; +my %saved_opt; +my $daemon; + sub T_ANY { 'any' } sub T_STRING { 'string' } sub T_EMAIL { 'e-mail address' } @@ -829,105 +836,105 @@ my @opt = ( " project now maintained on https://github.com/ddclient/ddclient" ); -## process args -my $opt_usage = process_args(@opt); -my ($result, %config, %globals, %cache); -my $saved_cache = ''; -my %saved_opt = %opt; -$result = 'OK'; +sub main { + ## process args + my $opt_usage = process_args(@opt); + $saved_cache = ''; + %saved_opt = %opt; + $result = 'OK'; -test_geturl(opt('geturl')) if opt('geturl'); + test_geturl(opt('geturl')) if opt('geturl'); -if (opt('help')) { - printf "%s\n", $opt_usage; - exit 0; -} - -## read config file because 'daemon' mode may be defined there. -read_config($opt{'file'} // default('file'), \%config, \%globals); -init_config(); -test_possible_ip() if opt('query'); - -my $caught_hup = 0; -my $caught_term = 0; -my $caught_int = 0; -$SIG{'HUP'} = sub { $caught_hup = 1; }; -$SIG{'TERM'} = sub { $caught_term = 1; }; -$SIG{'INT'} = sub { $caught_int = 1; }; -# don't fork() if foreground -if (opt('foreground')) { - ; -} elsif (opt('daemon')) { - $SIG{'CHLD'} = 'IGNORE'; - my $pid = fork; - if ($pid < 0) { - print STDERR "${program}: can not fork ($!)\n"; - exit -1; - } elsif ($pid) { + if (opt('help')) { + printf "%s\n", $opt_usage; exit 0; } - $SIG{'CHLD'} = 'DEFAULT'; - open(STDOUT, ">/dev/null"); - open(STDERR, ">/dev/null"); - open(STDIN, " 0) && !$caught_hup && !$caught_term && !$caught_int) { - my $delay = $left > 10 ? 10 : $left; - - $0 = sprintf("%s - sleeping for %s seconds", $program, $left); - $left -= sleep $delay; - # preventing deep sleep - see [bugs:#46] - if ($left > $daemon) { - $left = $daemon; - } + my $caught_hup = 0; + my $caught_term = 0; + my $caught_int = 0; + $SIG{'HUP'} = sub { $caught_hup = 1; }; + $SIG{'TERM'} = sub { $caught_term = 1; }; + $SIG{'INT'} = sub { $caught_int = 1; }; + # don't fork() if foreground + if (opt('foreground')) { + ; + } elsif (opt('daemon')) { + $SIG{'CHLD'} = 'IGNORE'; + my $pid = fork; + if ($pid < 0) { + print STDERR "${program}: can not fork ($!)\n"; + exit -1; + } elsif ($pid) { + exit 0; } - $caught_hup = 0; - $result = 0; - - } elsif (!scalar(%config)) { - warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon; - $result = 1; - - } else { - $result = $result eq 'OK' ? 0 : 1; + $SIG{'CHLD'} = 'DEFAULT'; + open(STDOUT, ">/dev/null"); + open(STDERR, ">/dev/null"); + open(STDIN, " 0) && !$caught_hup && !$caught_term && !$caught_int) { + my $delay = $left > 10 ? 10 : $left; + + $0 = sprintf("%s - sleeping for %s seconds", $program, $left); + $left -= sleep $delay; + # preventing deep sleep - see [bugs:#46] + if ($left > $daemon) { + $left = $daemon; + } + } + $caught_hup = 0; + $result = 0; + + } elsif (!scalar(%config)) { + warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon; + $result = 1; + + } else { + $result = $result eq 'OK' ? 0 : 1; + } + } while ($daemon && !$result && !$caught_term && !$caught_int); + + warning("caught SIGINT; exiting") if $caught_int; + unlink_pid(); + sendmail(); + + exit($result); +} ###################################################################### ## runpostscript @@ -5240,6 +5247,12 @@ sub nic_cloudns_update { } } +# Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR), +# otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module +# and test its functions directly; there's no need for test-only command-line arguments or stdout +# parsing. +__PACKAGE__->main() unless caller() && caller() ne 'PAR'; + ###################################################################### # vim: ai et ts=4 sw=4 tw=78: