EV-Websockets

 view release on metacpan or  search on metacpan

xt/author/autobahn.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use POSIX ();
use if -d 'blib', lib => 'blib/lib', 'blib/arch';

# Author test: WebSocket protocol conformance via the Autobahn TestSuite
# fuzzingclient, run against this module's echo server. Needs a container
# runtime (docker or podman; set $EV_WS_OCI to force one) and is Linux-oriented
# (uses --network host so the container can reach the host server).
# Run with: AUTHOR_TESTING=1 prove -lb xt/
#
# This asserts the CORE RFC 6455 conformance the module implements: framing,
# fragmentation, control frames, close handling, and limits. Some groups are
# excluded as out of scope (not conformance failures):
#   6.*, 7.5.*  UTF-8 validity (text + close reason): the module is
#               byte-transparent and does not enable lws's UTF-8 validation.
#   12.*, 13.*  permessage-deflate: compression is optional and not offered by
#               the server vhost (enabling it needs a server-tuned deflate
#               config -- the client-tuned one is non-conformant for a server).
#   2.10        burst of pings: lws coalesces auto-pongs, which Autobahn flags;
#               this is lws control-frame behaviour, not the module's logic.

plan skip_all => "set AUTHOR_TESTING=1 to run the Autobahn suite"
    unless $ENV{AUTHOR_TESTING};

# Container runtime: honour $EV_WS_OCI, else auto-detect docker or podman.
my $oci = $ENV{EV_WS_OCI};
unless ($oci) {
    for my $c (qw(docker podman)) {
        chomp(my $p = `command -v $c 2>/dev/null` || '');
        $oci = $c, last if $p;
    }
}
plan skip_all => "no container runtime (docker/podman) found" unless $oci;

# Create the shared temp dir BEFORE forking so parent and child agree on the
# path (the child publishes its port there; the parent reads it back).
require File::Temp;
require File::Spec;
my $dir = File::Temp->newdir(CLEANUP => 1);

my $forkpid = eval { fork() };
plan skip_all => "fork unavailable: $!" unless defined $forkpid;

if (!$forkpid) {
    # Child: echo server; publish the bound port, then service the loop.
    require EV;
    require EV::Websockets;
    my $ctx  = EV::Websockets::Context->new;
    my $port = $ctx->listen(
        port       => 0,
        max_message_size => 0,
        on_message => sub { my ($c, $d, $bin) = @_; $d //= ''; $bin ? $c->send_binary($d) : $c->send($d) },
    );
    open my $fh, '>', File::Spec->catfile("$dir", 'port') or POSIX::_exit(1);
    print $fh $port; close $fh;
    EV::run();
    POSIX::_exit(0);
}

# Parent: wait for the child to publish its port.
my $portfile = File::Spec->catfile("$dir", 'port');
my $port;
for (1 .. 50) {
    if (-s $portfile) { open my $fh, '<', $portfile; chomp($port = <$fh>); last }
    select undef, undef, undef, 0.1;
}

sub cleanup { kill 'TERM', $forkpid; waitpid $forkpid, 0 }

unless ($port) { cleanup(); plan skip_all => "echo server did not start" }

# fuzzingclient spec.
require JSON::PP;
my $spec = File::Spec->catfile("$dir", 'fuzzingclient.json');
open my $sf, '>', $spec or do { cleanup(); plan skip_all => "cannot write spec" };



( run in 1.024 second using v1.01-cache-2.11-cpan-7fcb06a456a )