Net-LibSSH
view release on metacpan or search on metacpan
.claude/agents/net-libssh-test-writer.md view on Meta::CPAN
```perl
use lib 't/lib';
use TestSSHD;
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect or diag 'connect: ' . ($ssh->error // '');
$ssh->auth_publickey($srv->client_key);
```
`knownhosts => '/dev/null'` is what keeps the ephemeral host key from being
written into the developer's real `known_hosts`. Every new integration test sets
it.
## The three traps that make a green suite meaningless
GETPROTOENT_R_PROTO|5.008000|5.008000|Vn
getpwent|5.009000||Viu
GETPWENT_R_HAS_BUFFER|5.008000||Viu
GETPWENT_R_HAS_FPTR|5.008000||Viu
GETPWENT_R_HAS_PTR|5.008000||Viu
GETPWENT_R_PROTO|5.008000|5.008000|Vn
getpwnam|5.009000||Viu
GETPWNAM_R_HAS_BUFFER|5.008000||Viu
GETPWNAM_R_HAS_PTR|5.008000||Viu
GETPWNAM_R_PROTO|5.008000|5.008000|Vn
getpwuid|5.009000||Viu
GETPWUID_R_HAS_PTR|5.008000||Viu
GETPWUID_R_PROTO|5.008000|5.008000|Vn
get_quantifier_value|5.033006||Viu
get_re_arg|||xciu
get_re_gclass_nonbitmap_data|5.031011||Viu
get_regclass_nonbitmap_data|5.031011||Viu
get_regex_charset_name|5.031004||Vniu
getservbyname|5.005000||Viu
GETSERVBYNAME_R_HAS_BUFFER|5.008000||Viu
GETSERVBYNAME_R_HAS_PTR|5.008000||Viu
KEY_getnetent|5.003007||Viu
KEY_getpeername|5.003007||Viu
KEY_getpgrp|5.003007||Viu
KEY_getppid|5.003007||Viu
KEY_getpriority|5.003007||Viu
KEY_getprotobyname|5.003007||Viu
KEY_getprotobynumber|5.003007||Viu
KEY_getprotoent|5.003007||Viu
KEY_getpwent|5.003007||Viu
KEY_getpwnam|5.003007||Viu
KEY_getpwuid|5.003007||Viu
KEY_getservbyname|5.003007||Viu
KEY_getservbyport|5.003007||Viu
KEY_getservent|5.003007||Viu
KEY_getsockname|5.003007||Viu
KEY_getsockopt|5.003007||Viu
KEY_getspnam|5.031011||Viu
KEY_given|5.009003||Viu
KEY_glob|5.003007||Viu
KEY_gmtime|5.003007||Viu
KEY_goto|5.003007||Viu
t/02-integration.t view on Meta::CPAN
my $srv = TestSSHD->start;
unless ($srv) {
plan skip_all => 'sshd or ssh-keygen not available';
}
# --- connect + auth ---
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
ok $ssh->connect, 'connect() succeeds'
or diag 'connect error: ' . ($ssh->error // '');
ok $ssh->auth_publickey($srv->client_key), 'auth_publickey() succeeds'
or diag 'auth error: ' . ($ssh->error // '');
# --- channel + exec ---
t/03-channel-after-close.t view on Meta::CPAN
# of an error. Each method must now croak.
my $srv = TestSSHD->start;
unless ($srv) {
plan skip_all => 'sshd or ssh-keygen not available';
}
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect
or plan skip_all => 'connect failed: ' . ($ssh->error // '');
$ssh->auth_publickey($srv->client_key)
or plan skip_all => 'auth failed: ' . ($ssh->error // '');
my $ch = $ssh->channel;
ok defined $ch, 'channel() returns object';
t/04-no-sftp.t view on Meta::CPAN
unless ($srv) {
plan skip_all => 'sshd or ssh-keygen not available';
}
ok !$srv->has_sftp, 'harness variant really has no sftp subsystem'
or plan skip_all => 'TestSSHD unexpectedly advertised sftp; aborting';
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
ok $ssh->connect, 'connect() succeeds against sftp-free sshd'
or diag 'connect error: ' . ($ssh->error // '');
ok $ssh->auth_publickey($srv->client_key), 'auth_publickey() succeeds'
or diag 'auth error: ' . ($ssh->error // '');
# --- exec-channel path works fully, with no SFTP subsystem present ---
t/05-channel-io.t view on Meta::CPAN
# read(undef) trap, write()+send_eof() into a command that blocks on
# stdin, eof() after the remote side closes, and binary data (NUL and
# high bytes) surviving the round trip unmangled.
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect
or plan skip_all => 'connect failed: ' . ($ssh->error // '');
$ssh->auth_publickey($srv->client_key)
or plan skip_all => 'auth failed: ' . ($ssh->error // '');
# --- read($length) ---
{
t/06-exit-status-ordering.t view on Meta::CPAN
# by an alarm() so that if a different libssh build blocks instead
# (rather than pumping the loop), this file fails fast with a diagnostic
# instead of hanging the suite.
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect
or plan skip_all => 'connect failed: ' . ($ssh->error // '');
$ssh->auth_publickey($srv->client_key)
or plan skip_all => 'auth failed: ' . ($ssh->error // '');
sub with_timeout {
my ($seconds, $code) = @_;
my $result;
t/07-refcount-chain.t view on Meta::CPAN
# Every scenario runs in a forked child, so a regression is reported as
# a failing test instead of taking `prove` down with a SIGSEGV.
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
sub connect_session {
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect
or die 'connect: ' . ($ssh->error // '') . "\n";
$ssh->auth_publickey($srv->client_key)
or die 'auth: ' . ($ssh->error // '') . "\n";
return $ssh;
}
# Open $what on a fresh session, then lose the session variable the way
# $mode says. In every mode the session variable is gone by the time the
t/08-disconnect-invalidates.t view on Meta::CPAN
# Every scenario runs in a forked child, so a regression is a failing test
# instead of `prove` going down with a SIGSEGV.
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
sub connect_session {
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->connect
or die 'connect: ' . ($ssh->error // '') . "\n";
$ssh->auth_publickey($srv->client_key)
or die 'auth: ' . ($ssh->error // '') . "\n";
return $ssh;
}
# Run $code in a forked child and check the one line it writes back against
# $expect (a string compared for equality, or a Regexp matched against it).
t/09-connect-after-disconnect.t view on Meta::CPAN
my $srv = TestSSHD->start;
plan skip_all => 'sshd or ssh-keygen not available' unless $srv;
# timeout => 5 is what bounds this test: it is the timeout ssh_connect() would
# wait out on a spent session, so a regression costs five seconds and a failed
# assertion rather than hanging the suite.
sub session {
my $ssh = Net::LibSSH->new;
$ssh->option(host => $srv->host);
$ssh->option(port => $srv->port);
$ssh->option(user => scalar getpwuid($<));
$ssh->option(knownhosts => '/dev/null');
$ssh->option(timeout => 5);
return $ssh;
}
sub timed {
my ($code) = @_;
my $t0 = time;
my $rc = eval { $code->() };
my $err = $@;
t/lib/TestSSHD.pm view on Meta::CPAN
or return undef;
system('ssh-keygen', '-t', 'ed25519', '-N', '', '-f', "$dir/client_key", '-q') == 0
or return undef;
system('cp', "$dir/client_key.pub", "$dir/authorized_keys") == 0
or return undef;
chmod 0600, "$dir/authorized_keys";
my $port = _free_port() or return undef;
my $user = getpwuid($<);
my $cfg = "$dir/sshd_config";
open my $fh, '>', $cfg or return undef;
print $fh <<"CONFIG";
Port $port
HostKey $dir/host_key
AuthorizedKeysFile $dir/authorized_keys
PidFile $dir/sshd.pid
LogLevel ERROR
StrictModes no
( run in 0.475 second using v1.01-cache-2.11-cpan-7f9471e7e0a )