Skip to content

Commit

Permalink
Merge pull request ddclient#741 from rhansen/tests
Browse files Browse the repository at this point in the history
Unit test improvements
  • Loading branch information
rhansen authored Sep 6, 2024
2 parents a7abfcb + 5ed43a2 commit 490dc16
Show file tree
Hide file tree
Showing 18 changed files with 490 additions and 455 deletions.
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,6 @@ EXTRA_DIST += $(handwritten_tests) \
t/lib/ddclient/Test/Fake/HTTPD/dummy-server-cert.pem \
t/lib/ddclient/Test/Fake/HTTPD/dummy-server-key.pem \
t/lib/ddclient/t.pm \
t/lib/ddclient/t/HTTPD.pm \
t/lib/ddclient/t/ip.pm \
t/lib/ok.pm
1 change: 0 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ m4_foreach_w([_m], [
HTTP::Request
HTTP::Response
JSON::PP
LWP::UserAgent
Test::MockModule
Test::TCP
Test::Warnings
Expand Down
3 changes: 1 addition & 2 deletions ddclient.in
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ my $daemon_default = ($programd =~ /d$/) ? interval('5m') : undef;
# Current Logger instance. To push a context prefix onto the context stack:
# local _l = pushlogctx('additional context goes here');
our $_l = ddclient::Logger->new();
our @_test_headers;

$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";

Expand Down Expand Up @@ -2818,7 +2817,7 @@ sub geturl {
push(@curlopt, "user=\"".escape_curl_param("${login}:${password}").'"') if (defined($login) && defined($password));
push(@curlopt, "proxy=\"".escape_curl_param("${protocol}://${proxy}").'"') if defined($proxy);
push(@curlopt, "url=\"".escape_curl_param("${protocol}://${server}/${url}").'"');
push(@curlopt, map('header="' . escape_curl_param($_) . '"', @_test_headers,
push(@curlopt, map('header="' . escape_curl_param($_) . '"',
ref($headers) eq 'ARRAY' ? @$headers : split('\n', $headers)));

# Add in the data if any was provided (for POST/PATCH)
Expand Down
4 changes: 2 additions & 2 deletions t/builtinfw_query.pl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use Test::More;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }

my $got_host;
my $builtinfw = 't/builtinfw_query.pl';
Expand Down
9 changes: 2 additions & 7 deletions t/get_ip_from_if.pl
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
use Test::More;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
use ddclient::t;
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);

# To aid in debugging, uncomment the following lines. (They are normally left commented to avoid
# accidentally interfering with the Test Anything Protocol messages written by Test::More.)
#STDOUT->autoflush(1);
#$ddclient::globals{'debug'} = 1;

subtest "get_default_interface tests" => sub {
for my $sample (@ddclient::t::routing_samples) {
Expand Down
71 changes: 18 additions & 53 deletions t/geturl_connectivity.pl
Original file line number Diff line number Diff line change
@@ -1,58 +1,23 @@
use Test::More;
eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@);
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my $has_http_daemon_ssl = eval { require HTTP::Daemon::SSL; };
my $ipv6_supported = eval {
require IO::Socket::IP;
my $ipv6_socket = IO::Socket::IP->new(
Domain => 'PF_INET6',
LocalHost => '::1',
Listen => 1,
);
defined($ipv6_socket);
};

my $http_daemon_supports_ipv6 = eval {
require HTTP::Daemon;
HTTP::Daemon->VERSION(6.12);
};

# To aid in debugging, uncomment the following lines. (They are normally left commented to avoid
# accidentally interfering with the Test Anything Protocol messages written by Test::More.)
#STDOUT->autoflush(1);
#$ddclient::globals{'verbose'} = 1;
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
BEGIN {
eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@);
ddclient::t::HTTPD->import();
}
use ddclient::t::ip;

my $certdir = "$ENV{abs_top_srcdir}/t/lib/ddclient/Test/Fake/HTTPD";
$ddclient::globals{'ssl_ca_file'} = "$certdir/dummy-ca-cert.pem";
$ddclient::globals{'ssl_ca_file'} = $ca_file;

sub run_httpd {
my ($ipv6, $ssl) = @_;
return undef if $ssl && !$has_http_daemon_ssl;
return undef if $ipv6 && (!$ipv6_supported || !$http_daemon_supports_ipv6);
my $httpd = ddclient::Test::Fake::HTTPD->new(
host => $ipv6 ? '::1' : '127.0.0.1',
scheme => $ssl ? 'https' : 'http',
daemon_args => {
SSL_cert_file => "$certdir/dummy-server-cert.pem",
SSL_key_file => "$certdir/dummy-server-key.pem",
V6Only => 1,
},
);
$httpd->run(sub {
# Echo back the full request.
return [200, ['Content-Type' => 'application/octet-stream'], [$_[0]->as_string()]];
});
diag(sprintf("started IPv%s%s server running at %s",
$ipv6 ? '6' : '4', $ssl ? ' SSL' : '', $httpd->endpoint()));
return $httpd;
for my $ipv ('4', '6') {
for my $ssl (0, 1) {
my $httpd = httpd($ipv, $ssl) or next;
$httpd->run(sub {
return [200, ['Content-Type' => 'application/octet-stream'], [$_[0]->as_string()]];
});
}
}

my %httpd = (
'4' => {'http' => run_httpd(0, 0), 'https' => run_httpd(0, 1)},
'6' => {'http' => run_httpd(1, 0), 'https' => run_httpd(1, 1)},
);

my @test_cases = (
{ipv6_opt => 0, server_ipv => '4', client_ipv => ''},
{ipv6_opt => 0, server_ipv => '4', client_ipv => '4'},
Expand All @@ -79,9 +44,9 @@ sub run_httpd {
skip("IPv6 not supported on this system", 1)
if $tc->{server_ipv} eq '6' && !$ipv6_supported;
skip("HTTP::Daemon too old for IPv6 support", 1)
if $tc->{server_ipv} eq '6' && !$http_daemon_supports_ipv6;
skip("HTTP::Daemon::SSL not available", 1) if $tc->{ssl} && !$has_http_daemon_ssl;
my $uri = $httpd{$tc->{server_ipv}}{$tc->{ssl} ? 'https' : 'http'}->endpoint();
if $tc->{server_ipv} eq '6' && !$httpd_ipv6_supported;
skip("HTTP::Daemon::SSL not available", 1) if $tc->{ssl} && !$httpd_ssl_supported;
my $uri = httpd($tc->{server_ipv}, $tc->{ssl})->endpoint();
my $name = sprintf("IPv%s client to %s%s",
$tc->{client_ipv} || '*', $uri, $tc->{ipv6_opt} ? ' (-ipv6)' : '');
$ddclient::globals{'ipv6'} = $tc->{ipv6_opt};
Expand Down
2 changes: 2 additions & 0 deletions t/lib/ddclient/t.pm
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,5 @@ EOF
want_ipv6_if => "en0",
},
);

1;
135 changes: 135 additions & 0 deletions t/lib/ddclient/t/HTTPD.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package ddclient::t::HTTPD;

use v5.10.1;
use strict;
use warnings;

use parent qw(ddclient::Test::Fake::HTTPD);

use Exporter qw(import);
use JSON::PP;
use Test::More;
BEGIN { require 'ddclient'; }
use ddclient::t::ip;

our @EXPORT = qw(
httpd
httpd_ipv6_ok httpd_ipv6_required $httpd_ipv6_supported $httpd_ipv6_support_error
httpd_ssl_ok httpd_ssl_required $httpd_ssl_supported $httpd_ssl_support_error
$ca_file $certdir
$textplain
);

our $httpd_ssl_support_error;
our $httpd_ssl_supported = eval { require HTTP::Daemon::SSL; 1; } or $httpd_ssl_support_error = $@;

sub httpd_ssl_ok {
ok($httpd_ssl_supported, "SSL is supported") or diag($httpd_ssl_support_error);
}

sub httpd_ssl_required {
plan(skip_all => $httpd_ssl_support_error) if !$httpd_ssl_supported;
}

our $httpd_ipv6_support_error;
our $httpd_ipv6_supported = $ipv6_supported or $httpd_ipv6_support_error = $ipv6_support_error;
$httpd_ipv6_supported = eval { require HTTP::Daemon; HTTP::Daemon->VERSION(6.12); }
or $httpd_ipv6_support_error = $@
if $httpd_ipv6_supported;

sub httpd_ipv6_ok {
ok($httpd_ipv6_supported, "test HTTP server supports IPv6") or diag($httpd_ipv6_support_error);
}

sub httpd_ipv6_required {
plan(skip_all => $httpd_ipv6_support_error) if !$httpd_ipv6_supported;
}

our $textplain = ['content-type' => 'text/plain; charset=utf-8'];

sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_requests} = []; # Log of received requests.
$self->{_responses} = []; # Script of responses to play back.
return $self;
}

sub run {
my ($self, $app) = @_;
$self->SUPER::run(sub {
my ($req) = @_;
push(@{$self->{_requests}}, $req);
my $res = $app->($req) if defined($app);
return $res if defined($res);
if ($req->uri()->path() eq '/control') {
pop(@{$self->{_requests}});
if ($req->method() eq 'PUT') {
return [400, $textplain, ['content must be json']]
if $req->headers()->content_type() ne 'application/json';
eval { @{$self->{_responses}} = @{decode_json($req->content())}; 1; }
or return [400, $textplain, ['content is not valid json']];
@{$self->{_requests}} = ();
return [200, $textplain, ["successfully reset request log and response script"]];
} elsif ($req->method() eq 'GET') {
my @reqs = map($_->as_string(), @{$self->{_requests}});
return [200, ['content-type' => 'application/json'], [encode_json(\@reqs)]];
} else {
return [405, $textplain, ['unsupported method: ' . $req->method()]];
}
}
return shift(@{$self->{_responses}}) // [500, $textplain, ["no more scripted responses"]];
});
diag("started server running at " . $self->endpoint());
return $self;
}

sub reset {
my $self = shift;
my $ep = $self->endpoint();
my $got = ddclient::geturl(url => "$ep/control");
diag("http response:\n$got");
ddclient::header_ok($got)
or BAIL_OUT("failed to get log of requests from test http server at $ep");
$got =~ s/^.*?\n\n//s;
my @got = map(HTTP::Request->parse($_), @{decode_json($got)});
ddclient::header_ok(ddclient::geturl(
url => "$ep/control",
method => 'PUT',
headers => ['content-type: application/json'],
data => encode_json(\@_),
)) or BAIL_OUT("failed to reset the test http server at $ep");
return @got;
}

our $certdir = "$ENV{abs_top_srcdir}/t/lib/ddclient/Test/Fake/HTTPD";
our $ca_file = "$certdir/dummy-ca-cert.pem";

my %daemons;

sub httpd {
my ($ipv, $ssl) = @_;
$ipv //= '';
$ssl = !!$ssl;
return undef if $ipv eq '6' && !$httpd_ipv6_supported;
return undef if $ssl && !$httpd_ssl_supported;
if (!defined($daemons{$ipv}{$ssl})) {
my $host
= $ipv eq '4' ? '127.0.0.1'
: $ipv eq '6' ? '::1'
: $httpd_ipv6_supported ? '::1'
: '127.0.0.1';
$daemons{$ipv}{$ssl} = __PACKAGE__->new(
host => $host,
scheme => $ssl ? 'https' : 'http',
daemon_args => {
(V6Only => $ipv eq '6' ? 1 : 0) x ($host eq '::1'),
(SSL_cert_file => "$certdir/dummy-server-cert.pem",
SSL_key_file => "$certdir/dummy-server-key.pem") x $ssl,
},
);
}
return $daemons{$ipv}{$ssl};
}

1;
30 changes: 30 additions & 0 deletions t/lib/ddclient/t/ip.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ddclient::t::ip;

use v5.10.1;
use strict;
use warnings;
use Exporter qw(import);
use Test::More;

our @EXPORT = qw(ipv6_ok ipv6_required $ipv6_supported $ipv6_support_error);

our $ipv6_support_error;
our $ipv6_supported = eval {
require IO::Socket::IP;
my $ipv6_socket = IO::Socket::IP->new(
Domain => 'PF_INET6',
LocalHost => '::1',
Listen => 1,
);
defined($ipv6_socket);
} or $ipv6_support_error = $@;

sub ipv6_ok {
ok($ipv6_supported, "system supports IPv6") or diag($ipv6_support_error);
}

sub ipv6_required {
plan(skip_all => $ipv6_support_error) if !$ipv6_supported;
}

1;
20 changes: 10 additions & 10 deletions t/protocol_directnic.pl
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use Test::More;
eval { require JSON::PP; } or plan(skip_all => $@);
JSON::PP->import(qw(encode_json));
eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@);
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } }
BEGIN { eval { require JSON::PP; 1; } or plan(skip_all => $@); JSON::PP->import(); }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
BEGIN {
eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@);
ddclient::t::HTTPD->import();
}

ddclient::load_json_support('directnic');

my $httpd = ddclient::Test::Fake::HTTPD->new();
$httpd->run(sub {
httpd()->run(sub {
my ($req) = @_;
diag('==============================================================================');
diag("Test server received request:\n" . $req->as_string());
Expand All @@ -28,11 +29,10 @@
}
return [400, $headers, ['unexpected request: ' . $req->uri()]]
});
diag("started IPv4 HTTP server running at " . $httpd->endpoint());

{
package Logger;
BEGIN { push(our @ISA, qw(ddclient::Logger)); }
use parent qw(-norequire ddclient::Logger);
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new(undef, $parent);
Expand All @@ -47,7 +47,7 @@
}
}

my $hostname = $httpd->endpoint();
my $hostname = httpd()->endpoint();
my @test_cases = (
{
desc => 'IPv4, good',
Expand Down
Loading

0 comments on commit 490dc16

Please sign in to comment.