Trog-DataSource-ProvisionedVirt-spawn.t 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. #!/usr/bin/env perl
  2. # spawn() for real: it forks, detaches, and runs a provisioner, which here is
  3. # a shell script in a sandbox. t/Trog-DataSource-ProvisionedVirt.t mocks it,
  4. # so this is the one place where its child is run.
  5. use 5.040;
  6. use re '/aasx';
  7. use warnings FATAL => 'all';
  8. use FindBin::libs;
  9. use Test::More;
  10. use File::Temp qw{tempdir};
  11. use File::Path ();
  12. use Time::HiRes ();
  13. our ( $ROOT, $OLDCWD );
  14. BEGIN {
  15. require Cwd;
  16. $OLDCWD = Cwd::getcwd();
  17. $ROOT = tempdir( 'tcms-spawn-XXXXXX', TMPDIR => 1, CLEANUP => 1 );
  18. $ENV{HOME} = $ROOT;
  19. }
  20. require_ok('Trog::DataSource::ProvisionedVirt') or BAIL_OUT("Can't find SUT");
  21. END {
  22. chdir($OLDCWD) if $OLDCWD;
  23. }
  24. {
  25. no warnings qw{once};
  26. $Trog::DataSource::ProvisionedVirt::log_dir = "$ROOT/logs";
  27. }
  28. File::Path::make_path("$ROOT/bin");
  29. my sub provisioner ( $name, $body ) {
  30. my $program = "$ROOT/bin/$name";
  31. open( my $fh, '>', $program ) or die "$program: $!";
  32. print {$fh} "#!/bin/sh\n$body\n";
  33. close($fh) or die "$program: $!";
  34. chmod( 0755, $program ) or die "$program: $!";
  35. return $program;
  36. }
  37. my sub run ( $domain, $program, $credentials ) {
  38. my ( $ok, $why ) = Trog::DataSource::ProvisionedVirt::spawn( $domain, $credentials, $ROOT, [ $program, $domain ], 'admin' );
  39. ok( $ok, "a run for $domain starts" ) or diag($why);
  40. # What the run says once it has stopped, or undef if it has not stopped
  41. # within ten seconds.
  42. foreach ( 1 .. 100 ) {
  43. my $status = Trog::DataSource::ProvisionedVirt::status($domain);
  44. return $status if $status && $status->{state} ne 'running';
  45. Time::HiRes::sleep(0.1);
  46. }
  47. return;
  48. }
  49. # Starman, under which tPSGI runs, ignores SIGPIPE, and its children inherit
  50. # that. So a write to a provisioner that has gone fails rather than kills.
  51. local $SIG{PIPE} = 'IGNORE';
  52. subtest 'a provisioner that reads its credentials and succeeds' => sub {
  53. my $status = run( 'reads.test.test', provisioner( 'reads', 'cat >/dev/null; exit 0' ), { password => 'hunter2' } );
  54. is( $status->{state}, 'ok', 'the run is ok' );
  55. is( $status->{exit}, '0', 'and exited 0' );
  56. };
  57. subtest 'a provisioner that fails' => sub {
  58. my $status = run( 'fails.test.test', provisioner( 'fails', 'cat >/dev/null; exit 3' ), { password => 'hunter2' } );
  59. is( $status->{state}, 'failed', 'the run failed' );
  60. is( $status->{exit}, '3', 'with what the provisioner exited' );
  61. };
  62. # More than a pipe holds, so the write cannot finish before the provisioner
  63. # has gone, whichever of the two is first.
  64. subtest 'a provisioner that never reads its credentials' => sub {
  65. my $status = run( 'deaf.test.test', provisioner( 'deaf', 'exit 0' ), { password => 'x' x 1_048_576 } );
  66. is( $status->{state}, 'failed', 'the run failed, although the provisioner exited 0' );
  67. my $log = Trog::DataSource::ProvisionedVirt::log_for('deaf.test.test') // '';
  68. like( $log, qr/could[ ]not[ ]write[ ]to[ ]\N*deaf/, 'and the log says that it never got them' );
  69. };
  70. done_testing();