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

ppport.h  view on Meta::CPAN

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

ppport.h  view on Meta::CPAN

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 )