App-FuguVM

 view release on metacpan or  search on metacpan

t/fuguvm/autoinstall.t  view on Meta::CPAN

	open my $fh, '>', $file or die "Cannot write $file: $!";
	print $fh "System hostname = image\n";
	print $fh "HTTP proxy URL = \@PROXY_URL\@\n";
	close $fh;

	my $responder = App::FuguVM::Autoinstall->new(
		file      => $file,
		pidfile   => Fugu::Pidfile->new( path => "$dir/autoinstall.pid" ),
		store     => $store,
		proxy_url => 'http://10.0.2.2:8080',
		logfile   => "$dir/autoinstall.log",
	);

	my $port = $responder->start;
	skip 'no free port in the responder range', 16 if !defined $port;

	ok( $port >= 8181 && $port <= 8280, 'start returns a port of the range' );
	ok( $responder->is_running, 'the child is alive' );
	is( $responder->port, $port, 'port returns the recorded port' );
	is( $responder->start, $port,
		'a second start returns the port and starts nothing' );

	my $expected = "System hostname = image\n"
	    . "HTTP proxy URL = http://10.0.2.2:8080\n";

	my ( $head, $body ) = _request( $port, "GET /install.conf HTTP/1.1" );
	like( $head, qr{^HTTP/1\.1 200 }, 'GET /install.conf answers 200' );
	like( $head, qr{^Content-Type: text/plain\r?$}m,
		'with Content-Type: text/plain' );
	like( $head, qr{^Content-Length: @{[length $expected]}\r?$}m,
		'with the Content-Length of the rendered bytes' );
	is( $body, $expected, 'and the rendered bytes as the body' );

	( $head, $body ) = _request( $port, "HEAD /install.conf HTTP/1.1" );
	like( $head, qr{^HTTP/1\.1 200 }, 'HEAD answers 200' );
	is( $body, '', 'with the headers only' );

	($head) = _request( $port, "GET /elsewhere HTTP/1.1" );
	like( $head, qr{^HTTP/1\.1 404 }, 'another path answers 404' );

	($head) = _request( $port, "POST /install.conf HTTP/1.1" );
	like( $head, qr{^HTTP/1\.1 405 }, 'another method answers 405' );

	# The child binds the loopback address only, because the file
	# can hold a secret. The probe connects to the outbound address
	# of this host, and it skips when the host has none. Some
	# networks intercept and accept each connection. A refusal
	# cannot occur there, and the probe also skips.
	my $outbound = _outbound_address();
	if ( !defined $outbound || $outbound eq '127.0.0.1' ) {
		pass('no outbound address to probe, loopback bind unproven');
	}
	elsif ( _network_intercepts($outbound) ) {
		pass('the network accepts each connection, loopback bind unproven');
	}
	else {
		my $reached = IO::Socket::INET->new(
			PeerAddr => $outbound,
			PeerPort => $port,
			Proto    => 'tcp',
			Timeout  => 2,
		);
		is( $reached, undef,
			"a connection to $outbound:$port fails" );
	}

	ok( $responder->stop, 'stop returns 1' );
	ok( !$responder->is_running, 'the child is gone' );
	is( $responder->port, undef, 'port returns undef after stop' );
}

done_testing();

# _request($port, $line):
#	Send one request and return the head and the body.
sub _request ( $port, $line )
{
	my $sock = IO::Socket::INET->new(
		PeerAddr => '127.0.0.1',
		PeerPort => $port,
		Proto    => 'tcp',
		Timeout  => 5,
	) or die "Cannot connect to 127.0.0.1:$port: $!";

	print $sock "$line\r\nHost: 127.0.0.1\r\n\r\n";
	my $answer = do { local $/; <$sock> };
	close $sock;

	my ( $head, $body ) = split /\r\n\r\n/, $answer, 2;
	return ( $head, $body // '' );
}

# _outbound_address():
#	Return the local address of an outbound socket, or undef. The
#	datagram never leaves the host: connect on UDP only records
#	the route.
sub _outbound_address ()
{
	my $sock = IO::Socket::INET->new(
		PeerAddr => '203.0.113.1',
		PeerPort => 53,
		Proto    => 'udp',
	) or return;

	my $address = $sock->sockhost;
	close $sock;

	return $address;
}

# _network_intercepts($address):
#	Return 1 when a connection to a closed port on $address
#	succeeds. That network intercepts each connection, and a
#	bind probe proves nothing there.
sub _network_intercepts ($address)
{
	my $listener = IO::Socket::INET->new(
		LocalAddr => '127.0.0.1',
		LocalPort => 0,
		Proto     => 'tcp',
		Listen    => 1,
	) or return 0;

	my $closed = $listener->sockport;
	close $listener;

	my $sock = IO::Socket::INET->new(
		PeerAddr => $address,
		PeerPort => $closed,
		Proto    => 'tcp',
		Timeout  => 2,
	) or return 0;

	close $sock;

	return 1;
}



( run in 0.874 second using v1.01-cache-2.11-cpan-85d3896f969 )