Hypersonic
view release on metacpan or search on metacpan
t/0028-http2-streaming.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use XS::JIT::Builder;
use XS::JIT;
# ============================================================
# Phase 3: HTTP/2 DATA Frame Streaming Tests
# ============================================================
plan tests => 18;
# ============================================================
# Test 1-3: Module loads
# ============================================================
use_ok('Hypersonic');
use_ok('Hypersonic::Protocol::HTTP2');
use_ok('Hypersonic::Stream');
# ============================================================
# Compile Stream XS for testing
# ============================================================
{
my $builder = XS::JIT::Builder->new;
$builder->line('#include <string.h>')
->line('#include <sys/socket.h>')
->blank;
Hypersonic::Stream->generate_c_code($builder);
XS::JIT->compile(
code => $builder->code,
name => 'Hypersonic::Stream',
functions => Hypersonic::Stream->get_xs_functions,
);
}
# ============================================================
# Test 4: Check nghttp2 availability
# ============================================================
my $has_nghttp2 = Hypersonic::Protocol::HTTP2->check_nghttp2();
ok(defined $has_nghttp2, 'nghttp2 detection works');
diag($has_nghttp2 ? 'nghttp2 found' : 'nghttp2 not available');
# ============================================================
# Test 5-9: JIT code generation for HTTP/2 streaming
# ============================================================
SKIP: {
skip 'nghttp2 not available', 5 unless $has_nghttp2;
require XS::JIT::Builder;
subtest 'gen_stream_headers generates correct code' => sub {
plan tests => 4;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->gen_stream_headers($builder);
my $code = $builder->code;
like($code, qr/h2_stream_headers/, 'function defined');
like($code, qr/nghttp2_submit_headers/, 'uses submit_headers');
like($code, qr/NGHTTP2_FLAG_END_HEADERS/, 'has END_HEADERS flag');
unlike($code, qr/NGHTTP2_FLAG_END_STREAM/, 'no END_STREAM on headers');
};
subtest 'gen_stream_data generates correct code' => sub {
plan tests => 4;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->gen_stream_data($builder);
my $code = $builder->code;
like($code, qr/h2_stream_data/, 'function defined');
like($code, qr/H2ChunkProvider/, 'defines chunk provider');
like($code, qr/nghttp2_submit_data/, 'uses submit_data');
like($code, qr/NGHTTP2_FLAG_NONE/, 'uses FLAG_NONE (not END_STREAM)');
};
subtest 'gen_stream_end generates correct code' => sub {
plan tests => 2;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->gen_stream_end($builder);
my $code = $builder->code;
like($code, qr/h2_stream_end/, 'function defined');
like($code, qr/NGHTTP2_FLAG_END_STREAM/, 'uses END_STREAM flag');
};
subtest 'gen_flow_control generates correct code' => sub {
plan tests => 3;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->gen_flow_control($builder);
my $code = $builder->code;
like($code, qr/h2_can_send/, 'h2_can_send defined');
like($code, qr/h2_window_size/, 'h2_window_size defined');
like($code, qr/get_remote_window_size/, 'checks window size');
};
subtest 'gen_stream_xs_wrappers generates correct code' => sub {
plan tests => 3;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->gen_stream_xs_wrappers($builder);
my $code = $builder->code;
like($code, qr/hypersonic_h2_stream_start/, 'h2_stream_start XS wrapper');
like($code, qr/hypersonic_h2_stream_write/, 'h2_stream_write XS wrapper');
like($code, qr/hypersonic_h2_stream_end/, 'h2_stream_end XS wrapper');
};
}
# ============================================================
# Test 10-12: Stream.pm HTTP/2 protocol detection
# ============================================================
subtest 'Stream object with http2 protocol' => sub {
plan tests => 2;
my $stream = Hypersonic::Stream->new(
fd => 10,
protocol => 'http2',
);
is($stream->protocol, 'http2', 'protocol is http2');
is($stream->fd, 10, 'fd is set');
};
subtest 'Stream object with http1 protocol' => sub {
plan tests => 2;
my $stream = Hypersonic::Stream->new(
fd => 5,
protocol => 'http1',
);
is($stream->protocol, 'http1', 'protocol is http1');
is($stream->fd, 5, 'fd is set');
};
subtest 'Stream defaults to http1' => sub {
plan tests => 1;
my $stream = Hypersonic::Stream->new(fd => 1);
is($stream->protocol, 'http1', 'default protocol is http1');
};
# ============================================================
# Test 13-15: generate_streaming method
# ============================================================
SKIP: {
skip 'nghttp2 not available', 3 unless $has_nghttp2;
subtest 'generate_streaming generates all functions' => sub {
plan tests => 5;
require XS::JIT::Builder;
my $builder = XS::JIT::Builder->new;
Hypersonic::Protocol::HTTP2->generate_streaming($builder);
my $code = $builder->code;
like($code, qr/h2_stream_headers/, 'has h2_stream_headers');
like($code, qr/h2_stream_data/, 'has h2_stream_data');
like($code, qr/h2_stream_end/, 'has h2_stream_end');
like($code, qr/h2_can_send/, 'has h2_can_send');
like($code, qr/hypersonic_h2_stream/, 'has XS wrappers');
};
subtest 'Stream.generate_c_code with http2 option' => sub {
plan tests => 2;
require XS::JIT::Builder;
my $builder = XS::JIT::Builder->new;
Hypersonic::Stream->generate_c_code($builder, {
max_connections => 100,
http2 => 1,
});
my $code = $builder->code;
# HTTP/1.1 streaming code (always present)
like($code, qr/stream_start|stream_write_chunk/, 'has HTTP/1.1 streaming code');
# Stream registry tracks http2 flag for future HTTP/2 support
like($code, qr/int http2/, 'has http2 flag in StreamState');
};
subtest 'Stream.generate_c_code without http2 option' => sub {
plan tests => 2;
require XS::JIT::Builder;
my $builder = XS::JIT::Builder->new;
Hypersonic::Stream->generate_c_code($builder, {
max_connections => 100,
http2 => 0,
});
my $code = $builder->code;
# HTTP/1.1 streaming code
like($code, qr/stream_start|stream_write_chunk/, 'has HTTP/1.1 streaming code');
# No HTTP/2 streaming code
unlike($code, qr/h2_stream_headers/, 'no HTTP/2 streaming code');
};
}
# ============================================================
# Test 16-18: Integration with Hypersonic
# ============================================================
SKIP: {
skip 'nghttp2 not available', 3 unless $has_nghttp2;
# Check for TLS test certificates
my $cert_file = 't/certs/server.crt';
my $key_file = 't/certs/server.key';
my $has_certs = (-f $cert_file && -f $key_file);
SKIP: {
skip 'TLS certificates not found', 1 unless $has_certs;
subtest 'Hypersonic compile with http2 and streaming' => sub {
plan tests => 3;
my $app = Hypersonic->new(
http2 => 1,
tls => 1,
cert => $cert_file,
key => $key_file,
);
$app->get('/events' => sub {
my ($req, $stream) = @_;
$stream->write("data: test\n\n");
$stream->end();
}, { streaming => 1 });
$app->get('/' => sub { return { status => 200, body => 'hello' } });
eval { $app->compile() };
ok(!$@, 'compile succeeds') or diag($@);
ok($app->{http2}, 'http2 enabled');
ok($app->{route_analysis}{needs_streaming}, 'streaming detected');
};
}
SKIP: {
skip 'TLS certificates not found', 1 unless $has_certs;
subtest 'Hypersonic compile with http2, no streaming' => sub {
plan tests => 3;
my $app = Hypersonic->new(
http2 => 1,
tls => 1,
cert => $cert_file,
key => $key_file,
);
$app->get('/' => sub { return { status => 200, body => 'hello' } });
eval { $app->compile() };
ok(!$@, 'compile succeeds') or diag($@);
ok($app->{http2}, 'http2 enabled');
ok(!$app->{route_analysis}{needs_streaming}, 'no streaming detected');
};
}
subtest 'Hypersonic without http2 still compiles streaming' => sub {
plan tests => 3;
my $app = Hypersonic->new(http2 => 0);
$app->get('/stream' => sub {
my ($req, $stream) = @_;
$stream->write("chunk");
$stream->end();
}, { streaming => 1 });
$app->get('/' => sub { return 'hello' });
eval { $app->compile() };
ok(!$@, 'compile succeeds') or diag($@);
ok(!$app->{http2}, 'http2 disabled');
ok($app->{route_analysis}{needs_streaming}, 'streaming detected');
};
}
done_testing();
( run in 1.473 second using v1.01-cache-2.11-cpan-140bd7fdf52 )