Skip to content

Commit efe6dd3

Browse files
committed
tests: Add basic testing of output
Partly rewritten python code to perl based Test::Command framework. That allows to compare tools output, not just exit code and describes better the output. Also it can be run standalone (without meson). Covered mostly ping (but still needs to be improved). TODO: covert all python tests. Optional parameter allows to pass binary to be tested (path to binary would be better, but it'd have to be two directories: builddir/:builddir/ping/). ping-01-basics.t mock getaddrinfo() code in ping. Implements: iputils#243 Signed-off-by: Petr Vorel <pvorel@suse.cz>
1 parent 9839d0b commit efe6dd3

File tree

10 files changed

+338
-19
lines changed

10 files changed

+338
-19
lines changed

test/arping/arping-01-basic.t

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
use Test::Command tests => 2;
6+
use Test::More;
7+
8+
my $lib = File::Basename::dirname(Cwd::abs_path($0)) . '/../lib.pl';
9+
require "$lib";
10+
11+
my $arping = get_cmd($ARGV[0] // 'arping');
12+
13+
# -V
14+
{
15+
my $cmd = Test::Command->new(cmd => "$arping -V");
16+
$cmd->exit_is_num(0);
17+
subtest 'output' => sub {
18+
$cmd->stdout_like(qr/^arping from iputils /, 'Print version');
19+
$cmd->stdout_like(qr/libcap: (yes|no), IDN: (yes|no), NLS: (yes|no), error.h: (yes|no), getrandom\(\): (yes|no), __fpending\(\): (yes|no)$/, 'Print config');
20+
}
21+
}

test/arping/meson.build

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# SPDX-License-Identifier: GPL-2.0-or-later
2-
# Copyright (c) 2021 Petr Vorel <pvorel@suse.cz>
2+
# Copyright (c) 2021-2025 Petr Vorel <pvorel@suse.cz>
33

4-
cmd = arping
5-
cmd_name = 'arping '
6-
args = ['-V']
4+
message('Build directory is: ' + meson.current_build_dir())
75

8-
name = cmd_name + ' '.join(args)
9-
test(name, cmd, args : args)
6+
foreach t : ['arping-01-basic.t']
7+
test(t, find_program(t), args: arping)
8+
endforeach
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
use Test::Command tests => 2;
6+
use Test::More;
7+
8+
my $lib = File::Basename::dirname(Cwd::abs_path($0)) . '/../lib.pl';
9+
require "$lib";
10+
11+
my $clockdiff = get_cmd($ARGV[0] // 'clockdiff');
12+
13+
# -V
14+
{
15+
my $cmd = Test::Command->new(cmd => "$clockdiff -V");
16+
$cmd->exit_is_num(0);
17+
subtest 'output' => sub {
18+
$cmd->stdout_like(qr/^clockdiff from iputils /, 'Print version');
19+
$cmd->stdout_like(qr/libcap: (yes|no), IDN: (yes|no), NLS: (yes|no), error.h: (yes|no), getrandom\(\): (yes|no), __fpending\(\): (yes|no)$/, 'Print config');
20+
}
21+
}

test/clockdiff/meson.build

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# SPDX-License-Identifier: GPL-2.0-or-later
2-
# Copyright (c) 2021 Petr Vorel <pvorel@suse.cz>
2+
# Copyright (c) 2021-2025 Petr Vorel <pvorel@suse.cz>
33

4-
cmd = clockdiff
5-
cmd_name = 'clockdiff '
6-
args = ['-V']
4+
message('Build directory is: ' + meson.current_build_dir())
75

8-
name = cmd_name + ' '.join(args)
9-
test(name, cmd, args : args)
6+
foreach t : ['clockdiff-01-basic.t']
7+
test(t, find_program(t), args: clockdiff)
8+
endforeach

test/lib.pl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
our @EXPORT_OK = qw(get_cmd);
6+
7+
sub get_cmd()
8+
{
9+
my $cmd = shift;
10+
11+
diag("Running as UID: $>");
12+
diag("PATH = $ENV{PATH}");
13+
diag("passed cmd: $cmd");
14+
printf("# actually used cmd: ");
15+
system("/bin/sh", "-c", "command -v $cmd");
16+
17+
exit 77 if (defined($ENV{VARIANT}) && $ENV{VARIANT} eq 'cross-compile');
18+
19+
return "$cmd";
20+
}
21+
22+
1;

test/ping/meson.build

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# SPDX-License-Identifier: GPL-2.0-or-later
2-
# Copyright (c) 2021 Petr Vorel <pvorel@suse.cz>
2+
# Copyright (c) 2021-2025 Petr Vorel <pvorel@suse.cz>
33

4+
message('Build directory is: ' + meson.current_build_dir())
5+
6+
foreach t : ['ping-01-basics.t', 'ping-02-errors.t' ]
7+
test(t, find_program(t), args: ping)
8+
endforeach
9+
10+
# FIXME: convert to perl
411
# GitHub CI does not have working IPv6
512
# https://github.com/actions/virtual-environments/issues/668
613
ipv6_dst = []

test/ping/ping-01-basics.t

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
use Socket qw(AF_INET AF_INET6 AF_UNSPEC AI_IDN AI_CANONIDN AI_CANONNAME IPPROTO_UDP SOCK_STREAM);
6+
use Socket::GetAddrInfo qw(getaddrinfo);
7+
use Test::Command tests => 11;
8+
use Test::More;
9+
10+
my $lib = File::Basename::dirname(Cwd::abs_path($0)) . '/../lib.pl';
11+
require "$lib";
12+
13+
my $ping = get_cmd($ARGV[0] // 'ping');
14+
15+
# Mock getaddrinfo() related code in ping/ping.c main() followed by logic in
16+
# ping4_run() and ping6_run().
17+
sub get_target
18+
{
19+
my $target = shift;
20+
my $target_ai_family = shift // AF_UNSPEC;
21+
22+
# ping/ping.c main(): getaddrinfo() to detect IPv4 vs. IPv6
23+
my $hints = {
24+
flags => AI_CANONNAME,
25+
family => $target_ai_family,
26+
socktype => SOCK_STREAM,
27+
};
28+
29+
die "invalid family: '$target_ai_family'" unless ($target_ai_family == AF_INET
30+
|| $target_ai_family == AF_INET6 || $target_ai_family == AF_UNSPEC);
31+
32+
my ($err, @res) = getaddrinfo($target, undef, $hints);
33+
die "getaddrinfo error: $err\n" if $err;
34+
35+
foreach my $ai (@res) {
36+
next if $target_ai_family != AF_UNSPEC && $target_ai_family != $ai->{family};
37+
38+
# ping/ping.c ping6_run(): AF_INET6 does not perform getaddrinfo()
39+
return $target if ($ai->{family} == AF_INET6);
40+
41+
# ping/ping6_common.c ping4_run(): AF_INET has extra getaddrinfo() to
42+
# get canonname.
43+
$hints = {
44+
ai_family => AF_INET,
45+
ai_protocol => IPPROTO_UDP,
46+
# getaddrinfo_flags: USE_IDN=true adds AI_IDN | AI_CANONIDN
47+
ai_flags => AI_CANONNAME | AI_IDN | AI_CANONIDN,
48+
};
49+
my ($err, @res) = getaddrinfo($target, undef, $hints);
50+
die "getaddrinfo error: $err\n" if $err;
51+
52+
return defined($ai->{canonname}) ? $ai->{canonname} : $target;
53+
}
54+
}
55+
56+
# -V
57+
{
58+
my $cmd = Test::Command->new(cmd => "$ping -V");
59+
$cmd->exit_is_num(0);
60+
subtest 'output' => sub {
61+
$cmd->stdout_like(qr/^ping from iputils /, 'Print version');
62+
$cmd->stdout_like(qr/libcap: (yes|no), IDN: (yes|no), NLS: (yes|no), error.h: (yes|no), getrandom\(\): (yes|no), __fpending\(\): (yes|no)$/, 'Print config');
63+
}
64+
}
65+
66+
# 127.0.0.1
67+
{
68+
my $cmd = Test::Command->new(cmd => "$ping -c1 127.0.0.1");
69+
$cmd->exit_is_num(0);
70+
subtest 'output' => sub {
71+
$cmd->stdout_like(qr/64 bytes from 127\.0\.0\.1/, 'Ping received from 127.0.0.1');
72+
$cmd->stdout_like(qr/0% packet loss/, 'No packet loss');
73+
$cmd->stdout_like(qr/time=\d+\.\d+ ms/, 'Ping time present');
74+
$cmd->stdout_like(qr~rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$~,
75+
'RTT time present');
76+
$cmd->stdout_like(qr{^PING 127\.0\.0\.1 \(127\.0\.0\.1\) 56\(84\) bytes of data\.
77+
64 bytes from 127\.0\.0\.1: icmp_seq=1 ttl=\d+ time=\d\.\d{3} ms
78+
79+
--- 127.0.0.1 ping statistics ---
80+
1 packets transmitted, 1 received, 0% packet loss, time \d+ms
81+
rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$},
82+
'Entire ping output matched exactly');
83+
}
84+
}
85+
86+
# ::1
87+
SKIP: {
88+
if ($ENV{SKIP_IPV6}) {
89+
skip 'IPv6 tests', 2;
90+
}
91+
my $cmd = Test::Command->new(cmd => "$ping -c1 ::1");
92+
$cmd->exit_is_num(0);
93+
subtest 'output' => sub {
94+
$cmd->stdout_like(qr/64 bytes from ::1/, 'Ping received from ::1');
95+
$cmd->stdout_like(qr/0% packet loss/, 'No packet loss');
96+
$cmd->stdout_like(qr/time=\d+\.\d+ ms/, 'Ping time present');
97+
$cmd->stdout_like(qr~rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$~,
98+
'RTT time present');
99+
$cmd->stdout_like(qr{^PING ::1 \(::1\) 56 data bytes
100+
64 bytes from ::1: icmp_seq=1 ttl=\d+ time=\d\.\d{3} ms
101+
102+
--- ::1 ping statistics ---
103+
1 packets transmitted, 1 received, 0% packet loss, time \d+ms
104+
rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$},
105+
'Entire ping output matched exactly');
106+
}
107+
}
108+
109+
my $localhost = "localhost";
110+
my $localhost_target_ipv4 = get_target($localhost, AF_INET);
111+
my $localhost_target_ipv6 = get_target($localhost, AF_INET6);
112+
diag("localhost_cannon_ipv4: '$localhost_target_ipv4'");
113+
diag("localhost_cannon_ipv6: '$localhost_target_ipv6'");
114+
die "Undefined cannonical name for $localhost on IPv4" unless defined $localhost_target_ipv4;
115+
die "Undefined cannonical name for $localhost on IPv6" unless defined $localhost_target_ipv6;
116+
117+
# localhost
118+
{
119+
my $cmd = Test::Command->new(cmd => "$ping -c1 $localhost");
120+
$cmd->exit_is_num(0);
121+
}
122+
123+
# -4 localhost
124+
{
125+
my $cmd = Test::Command->new(cmd => "$ping -c1 -4 $localhost");
126+
$cmd->exit_is_num(0);
127+
subtest 'output' => sub {
128+
$cmd->stdout_like(qr/64 bytes from $localhost_target_ipv4 \(127\.0\.0\.1\)/, "Ping received from $localhost (IPv4)");
129+
$cmd->stdout_like(qr/0% packet loss/, 'No packet loss');
130+
$cmd->stdout_like(qr/time=\d+\.\d+ ms/, 'Ping time present');
131+
$cmd->stdout_like(qr~rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$~,
132+
'RTT time present');
133+
$cmd->stdout_like(qr{^PING $localhost_target_ipv4 \(127\.0\.0\.1\) 56\(84\) bytes of data\.
134+
64 bytes from $localhost_target_ipv4 \(127\.0\.0\.1\): icmp_seq=1 ttl=\d+ time=\d\.\d{3} ms
135+
136+
--- $localhost_target_ipv4 ping statistics ---
137+
1 packets transmitted, 1 received, 0% packet loss, time \d+ms
138+
rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$},
139+
'Entire ping output matched exactly');
140+
}
141+
}
142+
143+
# -6 localhost
144+
SKIP: {
145+
if ($ENV{SKIP_IPV6}) {
146+
skip 'IPv6 tests', 2;
147+
}
148+
my $cmd = Test::Command->new(cmd => "$ping -c1 -6 $localhost");
149+
$cmd->exit_is_num(0);
150+
subtest 'output' => sub {
151+
$cmd->stdout_like(qr/64 bytes from $localhost_target_ipv6 \(::1\)/, "Ping received from $localhost (IPv6)");
152+
$cmd->stdout_like(qr/0% packet loss/, 'No packet loss');
153+
$cmd->stdout_like(qr/time=\d+\.\d+ ms/, 'Ping time present');
154+
$cmd->stdout_like(qr~rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$~,
155+
'RTT time present');
156+
}
157+
}

test/ping/ping-02-errors.t

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
use Test::Command tests => 6;
6+
use Test::More;
7+
8+
my $lib = File::Basename::dirname(Cwd::abs_path($0)) . '/../lib.pl';
9+
require "$lib";
10+
11+
my $ping = get_cmd($ARGV[0] // 'ping');
12+
13+
# no arg
14+
{
15+
my $cmd = Test::Command->new(cmd => "$ping", env => { LC_ALL => 'C', LANG => 'C' });
16+
$cmd->exit_is_num(2);
17+
subtest 'output' => sub {
18+
$cmd->stderr_is_eq("$ping: usage error: Destination address required\n");
19+
}
20+
}
21+
22+
# -c1 -i0.001 127.0.0.1
23+
{
24+
my $cmd = Test::Command->new(cmd => "$ping -c1 -i0.001 127.0.0.1", env => { LC_ALL => 'C', LANG => 'C' });
25+
$cmd->exit_is_num($> == 0 ? 0 : 2);
26+
subtest 'output' => sub {
27+
if ($> == 0) {
28+
$cmd->stdout_like(qr/^PING 127\.0\.0\.1.*bytes of data\.$/m, 'Ping header');
29+
$cmd->stdout_like(qr/64 bytes from 127\.0\.0\.1: icmp_seq=1 ttl=\d+ time=\d+\.\d+ ms/m, 'Ping reply line');
30+
$cmd->stdout_like(qr/1 packets transmitted, 1 received, 0% packet loss/, 'Ping success summary');
31+
$cmd->stdout_like(qr{^PING 127\.0\.0\.1 \(127\.0\.0\.1\) 56\(84\) bytes of data\.
32+
64 bytes from 127\.0\.0\.1: icmp_seq=1 ttl=\d+ time=\d\.\d{3} ms
33+
34+
--- 127.0.0.1 ping statistics ---
35+
1 packets transmitted, 1 received, 0% packet loss, time \d+ms
36+
rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$},
37+
'Entire ping output matched exactly');
38+
} else {
39+
$cmd->stderr_like(qr/cannot flood/i);
40+
$cmd->stdout_like(qr/PING 127\.0\.0\.1/);
41+
$cmd->stderr_like(qr/use -i 0\.002/);
42+
$cmd->stderr_like(qr/.*ping: cannot flood, minimal interval for user must be >= 2 ms, use -i 0\.002 \(or higher\)/);
43+
}
44+
}
45+
}
46+
47+
# -c1 -i0.001 ::1
48+
SKIP: {
49+
if ($ENV{SKIP_IPV6}) {
50+
skip 'IPv6 tests', 2;
51+
}
52+
my $cmd = Test::Command->new(cmd => "$ping -c1 -i0.001 ::1", env => { LC_ALL => 'C', LANG => 'C' });
53+
$cmd->exit_is_num($> == 0 ? 0 : 2);
54+
subtest 'output' => sub {
55+
if ($> == 0) {
56+
$cmd->stdout_like(qr/^PING ::1 \(::1\) 56 data bytes$/m, 'Ping header');
57+
$cmd->stdout_like(qr/64 bytes from ::1: icmp_seq=1 ttl=\d+ time=\d+\.\d+ ms/m, 'Ping reply line');
58+
$cmd->stdout_like(qr/1 packets transmitted, 1 received, 0% packet loss/, 'Ping success summary');
59+
$cmd->stdout_like(qr{^PING ::1 \(::1\) 56 data bytes
60+
64 bytes from ::1: icmp_seq=1 ttl=\d+ time=\d\.\d{3} ms
61+
62+
--- ::1 ping statistics ---
63+
1 packets transmitted, 1 received, 0% packet loss, time \d+ms
64+
rtt min/avg/max/mdev = \d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3}/\d+\.\d{3} ms$},
65+
'Entire ping output matched exactly');
66+
} else {
67+
$cmd->stderr_like(qr/cannot flood/i);
68+
$cmd->stdout_like(qr/PING ::1/);
69+
$cmd->stderr_like(qr/use -i 0\.002/);
70+
$cmd->stderr_like(qr/.*ping: cannot flood, minimal interval for user must be >= 2 ms, use -i 0\.002 \(or higher\)/);
71+
}
72+
}
73+
}

test/tracepath/meson.build

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# SPDX-License-Identifier: GPL-2.0-or-later
2-
# Copyright (c) 2021 Petr Vorel <pvorel@suse.cz>
2+
# Copyright (c) 2021-2025 Petr Vorel <pvorel@suse.cz>
33

4-
cmd = tracepath
5-
cmd_name = 'tracepath '
6-
args = ['-V']
4+
message('Build directory is: ' + meson.current_build_dir())
75

8-
name = cmd_name + ' '.join(args)
9-
test(name, cmd, args : args)
6+
foreach t : ['tracepath-01-basic.t']
7+
test(t, find_program(t), args: tracepath)
8+
endforeach
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/perl -w
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
# Copyright (c) 2025 Petr Vorel <pvorel@suse.cz>
4+
5+
use Test::Command tests => 2;
6+
use Test::More;
7+
8+
my $lib = File::Basename::dirname(Cwd::abs_path($0)) . '/../lib.pl';
9+
require "$lib";
10+
11+
my $tracepath = get_cmd($ARGV[0] // 'tracepath');
12+
13+
# -V
14+
{
15+
my $cmd = Test::Command->new(cmd => "$tracepath -V");
16+
$cmd->exit_is_num(0);
17+
subtest 'output' => sub {
18+
$cmd->stdout_like(qr/^tracepath from iputils /, 'Print version');
19+
$cmd->stdout_like(qr/libcap: (yes|no), IDN: (yes|no), NLS: (yes|no), error.h: (yes|no), getrandom\(\): (yes|no), __fpending\(\): (yes|no)$/, 'Print config');
20+
}
21+
}

0 commit comments

Comments
 (0)